2005-07-26 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlConnector.cs
1 //      Copyright (C) 2002 The Npgsql Development Team
2 //      npgsql-general@gborg.postgresql.org
3 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 //
19 //      Connector.cs
20 // ------------------------------------------------------------------
21 //      Project
22 //              Npgsql
23 //      Status
24 //              0.00.0000 - 06/17/2002 - ulrich sprick - created
25 //                        - 06/??/2004 - Glen Parker<glenebob@nwlink.com> rewritten
26
27 using System;
28 using System.Collections;
29 using System.IO;
30 using System.Text;
31 using System.Data;
32 using System.Security;
33 using System.Security.Cryptography;
34 using System.Security.Cryptography.X509Certificates;
35
36 using Mono.Security.Protocol.Tls;
37
38 using NpgsqlTypes;
39
40 namespace Npgsql
41 {
42     /// <summary>
43     /// !!! Helper class, for compilation only.
44     /// Connector implements the logic for the Connection Objects to
45     /// access the physical connection to the database, and isolate
46     /// the application developer from connection pooling internals.
47     /// </summary>
48     internal class NpgsqlConnector
49     {
50         // Immutable.
51         internal NpgsqlConnectionString                ConnectionString;
52
53         /// <summary>
54         /// Occurs on NoticeResponses from the PostgreSQL backend.
55         /// </summary>
56         internal event NoticeEventHandler                                Notice;
57
58         /// <summary>
59         /// Occurs on NotificationResponses from the PostgreSQL backend.
60         /// </summary>
61         internal event NotificationEventHandler        Notification;
62
63         /// <summary>
64         /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
65         /// </summary>
66         internal event CertificateSelectionCallback    CertificateSelectionCallback;
67
68         /// <summary>
69         /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
70         /// </summary>
71         internal event CertificateValidationCallback   CertificateValidationCallback;
72
73         /// <summary>
74         /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
75         /// </summary>
76         internal event PrivateKeySelectionCallback     PrivateKeySelectionCallback;
77
78         private ConnectionState                  _connection_state;
79
80         // The physical network connection to the backend.
81         private Stream                           _stream;
82
83         // Mediator which will hold data generated from backend.
84         private NpgsqlMediator                   _mediator;
85
86         private ProtocolVersion                  _backendProtocolVersion;
87         private ServerVersion                    _serverVersion;
88
89         // Values for possible CancelRequest messages.
90         private NpgsqlBackEndKeyData             _backend_keydata;
91
92         // Flag for transaction status.
93         //        private Boolean                         _inTransaction = false;
94         private NpgsqlTransaction                _transaction = null;
95
96         private Boolean                          _supportsPrepare = false;
97
98         private NpgsqlBackendTypeMapping         _oidToNameMapping = null;
99
100         private Encoding                         _encoding;
101
102         private Boolean                          _isInitialized;
103
104         private Boolean                          _pooled;
105         private Boolean                          _shared;
106
107         private NpgsqlState                      _state;
108
109
110         private Int32                            _planIndex;
111         private Int32                            _portalIndex;
112
113         private const String                     _planNamePrefix = "npgsqlplan";
114         private const String                     _portalNamePrefix = "npgsqlportal";
115
116
117
118         /// <summary>
119         /// Constructor.
120         /// </summary>
121         /// <param name="Shared">Controls whether the connector can be shared.</param>
122         public NpgsqlConnector(NpgsqlConnectionString ConnectionString, bool Pooled, bool Shared)
123         {
124             this.ConnectionString = ConnectionString;
125             _connection_state = ConnectionState.Closed;
126             _pooled = Pooled;
127             _shared = Shared;
128             _isInitialized = false;
129             _state = NpgsqlClosedState.Instance;
130             _mediator = new NpgsqlMediator();
131             _oidToNameMapping = new NpgsqlBackendTypeMapping();
132             _planIndex = 0;
133             _portalIndex = 0;
134
135         }
136
137
138         internal String Host
139         {
140             get
141             {
142                 return ConnectionString.ToString(ConnectionStringKeys.Host);
143             }
144         }
145
146         internal Int32 Port
147         {
148             get
149             {
150                 return ConnectionString.ToInt32(ConnectionStringKeys.Port, ConnectionStringDefaults.Port);
151             }
152         }
153
154         internal String Database
155         {
156             get
157             {
158                 return ConnectionString.ToString(ConnectionStringKeys.Database, UserName);
159             }
160         }
161
162         internal String UserName
163         {
164             get
165             {
166                 return ConnectionString.ToString(ConnectionStringKeys.UserName);
167             }
168         }
169
170         internal String Password
171         {
172             get
173             {
174                 return ConnectionString.ToString(ConnectionStringKeys.Password);
175             }
176         }
177
178         internal Boolean SSL
179         {
180             get
181             {
182                 return ConnectionString.ToBool(ConnectionStringKeys.SSL);
183             }
184         }
185         
186         internal SslMode SslMode
187         {
188             get
189             {
190                 return ConnectionString.ToSslMode(ConnectionStringKeys.SslMode);
191             }
192         }
193         
194         
195
196         /// <summary>
197         /// Gets the current state of the connection.
198         /// </summary>
199         internal ConnectionState State {
200             get
201             {
202                 return _connection_state;
203             }
204         }
205
206
207         // State
208         internal void Query (NpgsqlCommand queryCommand)
209         {
210             CurrentState.Query(this, queryCommand );
211         }
212
213         internal void Authenticate (string password)
214         {
215             CurrentState.Authenticate(this, password );
216         }
217
218         internal void Parse (NpgsqlParse parse)
219         {
220             CurrentState.Parse(this, parse);
221         }
222
223         internal void Flush ()
224         {
225             CurrentState.Flush(this);
226         }
227
228         internal void Sync ()
229         {
230             CurrentState.Sync(this);
231         }
232
233         internal void Bind (NpgsqlBind bind)
234         {
235             CurrentState.Bind(this, bind);
236         }
237
238         internal void Execute (NpgsqlExecute execute)
239         {
240             CurrentState.Execute(this, execute);
241         }
242
243
244
245         /// <summary>
246         /// This method checks if the connector is still ok.
247         /// We try to send a simple query text, select 1 as ConnectionTest;
248         /// </summary>
249         
250         internal Boolean IsValid()
251         {
252             try
253             {
254                 // Here we use a fake NpgsqlCommand, just to send the test query string.
255                 Query(new NpgsqlCommand("select 1 as ConnectionTest"));
256                 
257                 // Clear mediator.
258                 Mediator.ResetResponses();
259                 Mediator.ResetExpectations();
260                 
261                 
262             }
263             catch
264             {
265                 return false;
266             }
267
268             return true;
269         }
270
271
272         /// <summary>
273         /// This method is responsible to release all portals used by this Connector.
274         /// </summary>
275         internal void ReleasePlansPortals()
276         {
277             Int32 i = 0;
278
279             if (_planIndex > 0)
280             {
281                 for(i = 1; i <= _planIndex; i++)
282                     Query(new NpgsqlCommand(String.Format("deallocate \"{0}\";", _planNamePrefix + i)));
283             }
284
285             _portalIndex = 0;
286             _planIndex = 0;
287
288
289         }
290
291
292
293         /// <summary>
294         /// Check for mediator errors (sent by backend) and throw the appropriate
295         /// exception if errors found.  This needs to be called after every interaction
296         /// with the backend.
297         /// </summary>
298         internal void CheckErrors()
299         {
300             if (_mediator.Errors.Count > 0)
301             {
302                 throw new NpgsqlException(_mediator.Errors);
303             }
304         }
305
306         /// <summary>
307         /// Check for notices and fire the appropiate events.
308         /// This needs to be called after every interaction
309         /// with the backend.
310         /// </summary>
311         internal void CheckNotices()
312         {
313             if (Notice != null)
314             {
315                 foreach (NpgsqlError E in _mediator.Notices)
316                 {
317                     Notice(this, new NpgsqlNoticeEventArgs(E));
318                 }
319             }
320         }
321
322         /// <summary>
323         /// Check for notifications and fire the appropiate events.
324         /// This needs to be called after every interaction
325         /// with the backend.
326         /// </summary>
327         internal void CheckNotifications()
328         {
329             if (Notification != null)
330             {
331                 foreach (NpgsqlNotificationEventArgs E in _mediator.Notifications)
332                 {
333                     Notification(this, E);
334                 }
335             }
336         }
337
338         /// <summary>
339         /// Check for errors AND notifications in one call.
340         /// </summary>
341         internal void CheckErrorsAndNotifications()
342         {
343             CheckNotices();
344             CheckNotifications();
345             CheckErrors();
346         }
347
348         /// <summary>
349         /// Default SSL CertificateSelectionCallback implementation.
350         /// </summary>
351         internal X509Certificate DefaultCertificateSelectionCallback(
352             X509CertificateCollection      clientCertificates,
353             X509Certificate                serverCertificate,
354             string                         targetHost,
355             X509CertificateCollection      serverRequestedCertificates)
356         {
357             if (CertificateSelectionCallback != null)
358             {
359                 return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
360             }
361             else
362             {
363                 return null;
364             }
365         }
366
367         /// <summary>
368         /// Default SSL CertificateValidationCallback implementation.
369         /// </summary>
370         internal bool DefaultCertificateValidationCallback(
371             X509Certificate       certificate,
372             int[]                 certificateErrors)
373         {
374             if (CertificateValidationCallback != null)
375             {
376                 return CertificateValidationCallback(certificate, certificateErrors);
377             }
378             else
379             {
380                 return true;
381             }
382         }
383
384         /// <summary>
385         /// Default SSL PrivateKeySelectionCallback implementation.
386         /// </summary>
387         internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(
388             X509Certificate                certificate,
389             string                         targetHost)
390         {
391             if (PrivateKeySelectionCallback != null)
392             {
393                 return PrivateKeySelectionCallback(certificate, targetHost);
394             }
395             else
396             {
397                 return null;
398             }
399         }
400
401         /// <summary>
402         /// Version of backend server this connector is connected to.
403         /// </summary>
404         internal ServerVersion ServerVersion
405         {
406             get
407             {
408                 return _serverVersion;
409             }
410             set
411             {
412                 _serverVersion = value;
413             }
414         }
415
416         internal Encoding Encoding
417         {
418             get
419             {
420                 return _encoding;
421             }
422             set
423             {
424                 _encoding = value;
425             }
426         }
427
428         /// <summary>
429         /// Backend protocol version in use by this connector.
430         /// </summary>
431         internal ProtocolVersion BackendProtocolVersion
432         {
433             get
434             {
435                 return _backendProtocolVersion;
436             }
437             set
438             {
439                 _backendProtocolVersion = value;
440             }
441         }
442
443         /// <summary>
444         /// The physical connection stream to the backend.
445         /// </summary>
446         internal Stream Stream {
447             get
448             {
449                 return _stream;
450             }
451             set
452             {
453                 _stream = value;
454             }
455         }
456
457         /// <summary>
458         /// Reports if this connector is fully connected.
459         /// </summary>
460         internal Boolean IsInitialized
461         {
462             get
463             {
464                 return _isInitialized;
465             }
466             set
467             {
468                 _isInitialized = value;
469             }
470         }
471
472         internal NpgsqlState CurrentState {
473             get
474             {
475                 return _state;
476             }
477             set
478             {
479                 _state = value;
480             }
481         }
482
483
484         internal bool Pooled
485         {
486             get
487             {
488                 return _pooled;
489             }
490         }
491
492         internal bool Shared
493         {
494             get
495             {
496                 return _shared;
497             }
498         }
499
500         internal NpgsqlBackEndKeyData BackEndKeyData {
501             get
502             {
503                 return _backend_keydata;
504             }
505         }
506
507         internal NpgsqlBackendTypeMapping OidToNameMapping {
508             get
509             {
510                 return _oidToNameMapping;
511             }
512         }
513
514         /// <summary>
515         /// The connection mediator.
516         /// </summary>
517         internal NpgsqlMediator Mediator {
518             get
519             {
520                 return _mediator;
521             }
522         }
523
524         /// <summary>
525         /// Report if the connection is in a transaction.
526         /// </summary>
527         internal NpgsqlTransaction Transaction {
528             get
529             {
530                 return _transaction;
531             }
532             set
533             {
534                 _transaction = value;
535             }
536         }
537
538         /// <summary>
539         /// Report whether the current connection can support prepare functionality.
540         /// </summary>
541         internal Boolean SupportsPrepare {
542             get
543             {
544                 return _supportsPrepare;
545             }
546             set
547             {
548                 _supportsPrepare = value;
549             }
550         }
551
552         /// <summary>
553         /// This method is required to set all the version dependent features flags.
554         /// SupportsPrepare means the server can use prepared query plans (7.3+)
555         /// </summary>
556         // FIXME - should be private
557         internal void ProcessServerVersion ()
558         {
559             this._supportsPrepare = (ServerVersion >= new ServerVersion(7, 3, 0));
560         }
561
562         /// <value>Counts the numbers of Connections that share
563         /// this Connector. Used in Release() to decide wether this
564         /// connector is to be moved to the PooledConnectors list.</value>
565         // internal int mShareCount;
566
567         /// <summary>
568         /// Opens the physical connection to the server.
569         /// </summary>
570         /// <remarks>Usually called by the RequestConnector
571         /// Method of the connection pool manager.</remarks>
572         internal void Open()
573         {
574             ProtocolVersion      PV;
575
576             // If Connection.ConnectionString specifies a protocol version, we will
577             // not try to fall back to version 2 on failure.
578             if (ConnectionString.Contains(ConnectionStringKeys.Protocol))
579             {
580                 PV = ConnectionString.ToProtocolVersion(ConnectionStringKeys.Protocol);
581             }
582             else
583             {
584                 PV = ProtocolVersion.Unknown;
585             }
586
587             _backendProtocolVersion = (PV == ProtocolVersion.Unknown) ? ProtocolVersion.Version3 : PV;
588
589             // Reset state to initialize new connector in pool.
590             Encoding = Encoding.Default;
591             CurrentState = NpgsqlClosedState.Instance;
592
593             // Get a raw connection, possibly SSL...
594             CurrentState.Open(this);
595             // Establish protocol communication and handle authentication...
596             CurrentState.Startup(this);
597
598             // Check for protocol not supported.  If we have been told what protocol to use,
599             // we will not try this step.
600             if (_mediator.Errors.Count > 0 && PV == ProtocolVersion.Unknown)
601             {
602                 // If we attempted protocol version 3, it may be possible to drop back to version 2.
603                 if (BackendProtocolVersion == ProtocolVersion.Version3)
604                 {
605                     NpgsqlError       Error0 = (NpgsqlError)_mediator.Errors[0];
606
607                     // If NpgsqlError.ReadFromStream_Ver_3() encounters a version 2 error,
608                     // it will set its own protocol version to version 2.  That way, we can tell
609                     // easily if the error was a FATAL: protocol error.
610                     if (Error0.BackendProtocolVersion == ProtocolVersion.Version2)
611                     {
612                         // Try using the 2.0 protocol.
613                         _mediator.ResetResponses();
614                         BackendProtocolVersion = ProtocolVersion.Version2;
615                         CurrentState = NpgsqlClosedState.Instance;
616
617                         // Get a raw connection, possibly SSL...
618                         CurrentState.Open(this);
619                         // Establish protocol communication and handle authentication...
620                         CurrentState.Startup(this);
621                     }
622                 }
623             }
624
625             // Check for errors and do the Right Thing.
626             // FIXME - CheckErrors needs to be moved to Connector
627             CheckErrors();
628
629             _backend_keydata = _mediator.BackendKeyData;
630
631             // Change the state of connection to open and ready.
632             _connection_state = ConnectionState.Open;
633             CurrentState = NpgsqlReadyState.Instance;
634
635             String       ServerVersionString = String.Empty;
636
637             // First try to determine backend server version using the newest method.
638             if (((NpgsqlParameterStatus)_mediator.Parameters["__npgsql_server_version"]) != null)
639                 ServerVersionString = ((NpgsqlParameterStatus)_mediator.Parameters["__npgsql_server_version"]).ParameterValue;
640
641
642             // Fall back to the old way, SELECT VERSION().
643             // This should not happen for protocol version 3+.
644             if (ServerVersionString.Length == 0)
645             {
646                 NpgsqlCommand command = new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
647                 ServerVersionString = PGUtil.ExtractServerVersion( (String)command.ExecuteScalar() );
648             }
649
650             // Cook version string so we can use it for enabling/disabling things based on
651             // backend version.
652             ServerVersion = PGUtil.ParseServerVersion(ServerVersionString);
653
654             // Adjust client encoding.
655
656             //NpgsqlCommand commandEncoding1 = new NpgsqlCommand("show client_encoding", _connector);
657             //String clientEncoding1 = (String)commandEncoding1.ExecuteScalar();
658
659             if (ConnectionString.ToString(ConnectionStringKeys.Encoding, ConnectionStringDefaults.Encoding).ToUpper() == "UNICODE")
660             {
661                 Encoding = Encoding.UTF8;
662                 NpgsqlCommand commandEncoding = new NpgsqlCommand("SET CLIENT_ENCODING TO UNICODE", this);
663                 commandEncoding.ExecuteNonQuery();
664             }
665
666             // Make a shallow copy of the type mapping that the connector will own.
667             // It is possible that the connector may add types to its private
668             // mapping that will not be valid to another connector, even
669             // if connected to the same backend version.
670             _oidToNameMapping = NpgsqlTypesHelper.CreateAndLoadInitialTypesMapping(this).Clone();
671
672             ProcessServerVersion();
673
674             // The connector is now fully initialized. Beyond this point, it is
675             // safe to release it back to the pool rather than closing it.
676             IsInitialized = true;
677         }
678
679
680         /// <summary>
681         /// Closes the physical connection to the server.
682         /// </summary>
683         internal void Close()
684         {
685             try
686             {
687                 this.CurrentState.Close(this);
688             }
689             catch {}
690         }
691
692
693     ///<summary>
694     /// Returns next portal index.
695     ///</summary>
696     internal String NextPortalName()
697         {
698             
699             return _portalNamePrefix + System.Threading.Interlocked.Increment(ref _portalIndex);
700         }
701
702
703         ///<summary>
704         /// Returns next plan index.
705         ///</summary>
706         internal String NextPlanName()
707         {
708             return _planNamePrefix + System.Threading.Interlocked.Increment(ref _planIndex);
709         }
710
711
712
713     }
714 }