1 // Copyright (C) 2002 The Npgsql Development Team
2 // npgsql-general@gborg.postgresql.org
3 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
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.
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.
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
20 // ------------------------------------------------------------------
24 // 0.00.0000 - 06/17/2002 - ulrich sprick - created
25 // - 06/??/2004 - Glen Parker<glenebob@nwlink.com> rewritten
28 using System
.Collections
;
32 using System
.Security
;
33 using System
.Security
.Cryptography
;
34 using System
.Security
.Cryptography
.X509Certificates
;
35 using System
.Threading
;
36 using System
.Net
.Sockets
;
38 using Mono
.Security
.Protocol
.Tls
;
45 /// !!! Helper class, for compilation only.
46 /// Connector implements the logic for the Connection Objects to
47 /// access the physical connection to the database, and isolate
48 /// the application developer from connection pooling internals.
50 internal class NpgsqlConnector
53 internal NpgsqlConnectionString ConnectionString
;
56 /// Occurs on NoticeResponses from the PostgreSQL backend.
58 internal event NoticeEventHandler Notice
;
61 /// Occurs on NotificationResponses from the PostgreSQL backend.
63 internal event NotificationEventHandler Notification
;
66 /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
68 internal event CertificateSelectionCallback CertificateSelectionCallback
;
71 /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
73 internal event CertificateValidationCallback CertificateValidationCallback
;
76 /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
78 internal event PrivateKeySelectionCallback PrivateKeySelectionCallback
;
80 private ConnectionState _connection_state
;
82 // The physical network connection to the backend.
83 private Stream _stream
;
85 private Socket _socket
;
87 // Mediator which will hold data generated from backend.
88 private NpgsqlMediator _mediator
;
90 private ProtocolVersion _backendProtocolVersion
;
91 private ServerVersion _serverVersion
;
93 // Values for possible CancelRequest messages.
94 private NpgsqlBackEndKeyData _backend_keydata
;
96 // Flag for transaction status.
97 // private Boolean _inTransaction = false;
98 private NpgsqlTransaction _transaction
= null;
100 private Boolean _supportsPrepare
= false;
102 private NpgsqlBackendTypeMapping _oidToNameMapping
= null;
104 private Encoding _encoding
;
106 private Boolean _isInitialized
;
108 private Boolean _pooled
;
109 private Boolean _shared
;
111 private NpgsqlState _state
;
114 private Int32 _planIndex
;
115 private Int32 _portalIndex
;
117 private const String _planNamePrefix
= "npgsqlplan";
118 private const String _portalNamePrefix
= "npgsqlportal";
121 private Thread _notificationThread
;
123 // The AutoResetEvent to synchronize processing threads.
124 internal AutoResetEvent _notificationAutoResetEvent
;
126 // Counter of notification thread start/stop requests in order to
127 internal Int16 _notificationThreadStopCount
;
134 /// <param name="Shared">Controls whether the connector can be shared.</param>
135 public NpgsqlConnector(NpgsqlConnectionString ConnectionString
, bool Pooled
, bool Shared
)
137 this.ConnectionString
= ConnectionString
;
138 _connection_state
= ConnectionState
.Closed
;
141 _isInitialized
= false;
142 _state
= NpgsqlClosedState
.Instance
;
143 _mediator
= new NpgsqlMediator();
144 _oidToNameMapping
= new NpgsqlBackendTypeMapping();
147 _notificationThreadStopCount
= 1;
148 _notificationAutoResetEvent
= new AutoResetEvent(true);
157 return ConnectionString
.ToString(ConnectionStringKeys
.Host
);
165 return ConnectionString
.ToInt32(ConnectionStringKeys
.Port
, ConnectionStringDefaults
.Port
);
169 internal String Database
173 return ConnectionString
.ToString(ConnectionStringKeys
.Database
, UserName
);
177 internal String UserName
181 return ConnectionString
.ToString(ConnectionStringKeys
.UserName
);
185 internal String Password
189 return ConnectionString
.ToString(ConnectionStringKeys
.Password
);
197 return ConnectionString
.ToBool(ConnectionStringKeys
.SSL
);
201 internal SslMode SslMode
205 return ConnectionString
.ToSslMode(ConnectionStringKeys
.SslMode
);
209 internal Int32 ConnectionTimeout
213 return ConnectionString
.ToInt32(ConnectionStringKeys
.Timeout
, ConnectionStringDefaults
.Timeout
);
217 internal Int32 CommandTimeout
221 return ConnectionString
.ToInt32(ConnectionStringKeys
.CommandTimeout
, ConnectionStringDefaults
.CommandTimeout
);
227 /// Gets the current state of the connection.
229 internal ConnectionState State
{
232 return _connection_state
;
238 internal void Query (NpgsqlCommand queryCommand
)
240 CurrentState
.Query(this, queryCommand
);
243 internal void Authenticate (string password
)
245 CurrentState
.Authenticate(this, password
);
248 internal void Parse (NpgsqlParse parse
)
250 CurrentState
.Parse(this, parse
);
253 internal void Flush ()
255 CurrentState
.Flush(this);
258 internal void Sync ()
260 CurrentState
.Sync(this);
263 internal void Bind (NpgsqlBind bind
)
265 CurrentState
.Bind(this, bind
);
268 internal void Describe (NpgsqlDescribe describe
)
270 CurrentState
.Describe(this, describe
);
273 internal void Execute (NpgsqlExecute execute
)
275 CurrentState
.Execute(this, execute
);
281 /// This method checks if the connector is still ok.
282 /// We try to send a simple query text, select 1 as ConnectionTest;
285 internal Boolean
IsValid()
289 // Here we use a fake NpgsqlCommand, just to send the test query string.
291 Query(new NpgsqlCommand("select 1 as ConnectionTest", this));
294 Mediator
.ResetResponses();
295 Mediator
.ResetExpectations();
310 /// This method is responsible for releasing all resources associated with this Connector.
313 internal void ReleaseResources()
315 ReleasePlansPortals();
316 ReleaseRegisteredListen();
321 internal void ReleaseRegisteredListen()
323 Query(new NpgsqlCommand("unlisten *", this));
328 /// This method is responsible to release all portals used by this Connector.
330 internal void ReleasePlansPortals()
336 for(i
= 1; i
<= _planIndex
; i
++)
337 Query(new NpgsqlCommand(String
.Format("deallocate \"{0}\";", _planNamePrefix
+ i
), this));
351 /// Check for mediator errors (sent by backend) and throw the appropriate
352 /// exception if errors found. This needs to be called after every interaction
353 /// with the backend.
355 internal void CheckErrors()
357 if (_mediator
.Errors
.Count
> 0)
359 throw new NpgsqlException(_mediator
.Errors
);
364 /// Check for notices and fire the appropiate events.
365 /// This needs to be called after every interaction
366 /// with the backend.
368 internal void CheckNotices()
372 foreach (NpgsqlError E
in _mediator
.Notices
)
374 Notice(this, new NpgsqlNoticeEventArgs(E
));
380 /// Check for notifications and fire the appropiate events.
381 /// This needs to be called after every interaction
382 /// with the backend.
384 internal void CheckNotifications()
386 if (Notification
!= null)
389 foreach (NpgsqlNotificationEventArgs E
in _mediator
.Notifications
)
391 // Wrap our notification thread call while running on user land code.
392 // This prevents our thread of possibly dying there if there is no exception handling.
395 Notification(this, E
);
404 /// Check for errors AND notifications in one call.
406 internal void CheckErrorsAndNotifications()
409 CheckNotifications();
414 /// Default SSL CertificateSelectionCallback implementation.
416 internal X509Certificate
DefaultCertificateSelectionCallback(
417 X509CertificateCollection clientCertificates
,
418 X509Certificate serverCertificate
,
420 X509CertificateCollection serverRequestedCertificates
)
422 if (CertificateSelectionCallback
!= null)
424 return CertificateSelectionCallback(clientCertificates
, serverCertificate
, targetHost
, serverRequestedCertificates
);
433 /// Default SSL CertificateValidationCallback implementation.
435 internal bool DefaultCertificateValidationCallback(
436 X509Certificate certificate
,
437 int[] certificateErrors
)
439 if (CertificateValidationCallback
!= null)
441 return CertificateValidationCallback(certificate
, certificateErrors
);
450 /// Default SSL PrivateKeySelectionCallback implementation.
452 internal AsymmetricAlgorithm
DefaultPrivateKeySelectionCallback(
453 X509Certificate certificate
,
456 if (PrivateKeySelectionCallback
!= null)
458 return PrivateKeySelectionCallback(certificate
, targetHost
);
467 /// Version of backend server this connector is connected to.
469 internal ServerVersion ServerVersion
473 return _serverVersion
;
477 _serverVersion
= value;
481 internal Encoding Encoding
494 /// Backend protocol version in use by this connector.
496 internal ProtocolVersion BackendProtocolVersion
500 return _backendProtocolVersion
;
504 _backendProtocolVersion
= value;
509 /// The physical connection stream to the backend.
511 internal Stream Stream
{
523 /// The physical connection socket to the backend.
526 internal Socket Socket
{
538 /// Reports if this connector is fully connected.
540 internal Boolean IsInitialized
544 return _isInitialized
;
548 _isInitialized
= value;
552 internal NpgsqlState CurrentState
{
580 internal NpgsqlBackEndKeyData BackEndKeyData
{
583 return _backend_keydata
;
587 internal NpgsqlBackendTypeMapping OidToNameMapping
{
590 return _oidToNameMapping
;
595 /// The connection mediator.
597 internal NpgsqlMediator Mediator
{
605 /// Report if the connection is in a transaction.
607 internal NpgsqlTransaction Transaction
{
614 _transaction
= value;
619 /// Report whether the current connection can support prepare functionality.
621 internal Boolean SupportsPrepare
{
624 return _supportsPrepare
;
628 _supportsPrepare
= value;
633 /// This method is required to set all the version dependent features flags.
634 /// SupportsPrepare means the server can use prepared query plans (7.3+)
636 // FIXME - should be private
637 internal void ProcessServerVersion ()
639 this._supportsPrepare
= (ServerVersion
>= new ServerVersion(7, 3, 0));
642 /// <value>Counts the numbers of Connections that share
643 /// this Connector. Used in Release() to decide wether this
644 /// connector is to be moved to the PooledConnectors list.</value>
645 // internal int mShareCount;
648 /// Opens the physical connection to the server.
650 /// <remarks>Usually called by the RequestConnector
651 /// Method of the connection pool manager.</remarks>
656 // If Connection.ConnectionString specifies a protocol version, we will
657 // not try to fall back to version 2 on failure.
658 if (ConnectionString
.Contains(ConnectionStringKeys
.Protocol
))
660 PV
= ConnectionString
.ToProtocolVersion(ConnectionStringKeys
.Protocol
);
664 PV
= ProtocolVersion
.Unknown
;
667 _backendProtocolVersion
= (PV
== ProtocolVersion
.Unknown
) ? ProtocolVersion
.Version3
: PV
;
669 // Reset state to initialize new connector in pool.
670 Encoding
= Encoding
.Default
;
671 CurrentState
= NpgsqlClosedState
.Instance
;
673 // Get a raw connection, possibly SSL...
674 CurrentState
.Open(this);
675 // Establish protocol communication and handle authentication...
676 CurrentState
.Startup(this);
678 // Check for protocol not supported. If we have been told what protocol to use,
679 // we will not try this step.
680 if (_mediator
.Errors
.Count
> 0 && PV
== ProtocolVersion
.Unknown
)
682 // If we attempted protocol version 3, it may be possible to drop back to version 2.
683 if (BackendProtocolVersion
== ProtocolVersion
.Version3
)
685 NpgsqlError Error0
= (NpgsqlError
)_mediator
.Errors
[0];
687 // If NpgsqlError.ReadFromStream_Ver_3() encounters a version 2 error,
688 // it will set its own protocol version to version 2. That way, we can tell
689 // easily if the error was a FATAL: protocol error.
690 if (Error0
.BackendProtocolVersion
== ProtocolVersion
.Version2
)
692 // Try using the 2.0 protocol.
693 _mediator
.ResetResponses();
694 BackendProtocolVersion
= ProtocolVersion
.Version2
;
695 CurrentState
= NpgsqlClosedState
.Instance
;
697 // Get a raw connection, possibly SSL...
698 CurrentState
.Open(this);
699 // Establish protocol communication and handle authentication...
700 CurrentState
.Startup(this);
705 // Check for errors and do the Right Thing.
706 // FIXME - CheckErrors needs to be moved to Connector
709 _backend_keydata
= _mediator
.BackendKeyData
;
711 // Change the state of connection to open and ready.
712 _connection_state
= ConnectionState
.Open
;
713 CurrentState
= NpgsqlReadyState
.Instance
;
715 String ServerVersionString
= String
.Empty
;
717 // First try to determine backend server version using the newest method.
718 if (((NpgsqlParameterStatus
)_mediator
.Parameters
["__npgsql_server_version"]) != null)
719 ServerVersionString
= ((NpgsqlParameterStatus
)_mediator
.Parameters
["__npgsql_server_version"]).ParameterValue
;
722 // Fall back to the old way, SELECT VERSION().
723 // This should not happen for protocol version 3+.
724 if (ServerVersionString
.Length
== 0)
726 NpgsqlCommand command
= new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
727 ServerVersionString
= PGUtil
.ExtractServerVersion( (String
)command
.ExecuteScalar() );
730 // Cook version string so we can use it for enabling/disabling things based on
732 ServerVersion
= PGUtil
.ParseServerVersion(ServerVersionString
);
734 // Adjust client encoding.
736 //NpgsqlCommand commandEncoding1 = new NpgsqlCommand("show client_encoding", _connector);
737 //String clientEncoding1 = (String)commandEncoding1.ExecuteScalar();
739 if (ConnectionString
.ToString(ConnectionStringKeys
.Encoding
, ConnectionStringDefaults
.Encoding
).ToUpper() == "UNICODE")
741 Encoding
= Encoding
.UTF8
;
742 NpgsqlCommand commandEncoding
= new NpgsqlCommand("SET CLIENT_ENCODING TO UNICODE", this);
743 commandEncoding
.ExecuteNonQuery();
746 // Make a shallow copy of the type mapping that the connector will own.
747 // It is possible that the connector may add types to its private
748 // mapping that will not be valid to another connector, even
749 // if connected to the same backend version.
750 _oidToNameMapping
= NpgsqlTypesHelper
.CreateAndLoadInitialTypesMapping(this).Clone();
752 ProcessServerVersion();
754 // The connector is now fully initialized. Beyond this point, it is
755 // safe to release it back to the pool rather than closing it.
756 IsInitialized
= true;
761 /// Closes the physical connection to the server.
763 internal void Close()
767 this.CurrentState
.Close(this);
772 internal void CancelRequest()
775 NpgsqlConnector CancelConnector
= new NpgsqlConnector(ConnectionString
, false, false);
777 CancelConnector
._backend_keydata
= BackEndKeyData
;
780 // Get a raw connection, possibly SSL...
781 CancelConnector
.CurrentState
.Open(CancelConnector
);
783 // Cancel current request.
784 CancelConnector
.CurrentState
.CancelRequest(CancelConnector
);
791 /// Returns next portal index.
793 internal String
NextPortalName()
796 return _portalNamePrefix
+ System
.Threading
.Interlocked
.Increment(ref _portalIndex
);
801 /// Returns next plan index.
803 internal String
NextPlanName()
805 return _planNamePrefix
+ System
.Threading
.Interlocked
.Increment(ref _planIndex
);
809 internal void RemoveNotificationThread()
811 // Wait notification thread finish its work.
812 _notificationAutoResetEvent
.WaitOne();
814 // Kill notification thread.
815 _notificationThread
.Abort();
816 _notificationThread
= null;
818 // Special case in order to not get problems with thread synchronization.
819 // It will be turned to 0 when synch thread is created.
820 _notificationThreadStopCount
= 1;
824 internal void AddNotificationThread()
827 _notificationThreadStopCount
= 0;
828 _notificationAutoResetEvent
.Set();
830 NpgsqlContextHolder contextHolder
= new NpgsqlContextHolder(this, CurrentState
);
832 _notificationThread
= new Thread(new ThreadStart(contextHolder
.ProcessServerMessages
));
834 _notificationThread
.Start();
840 internal void StopNotificationThread()
843 _notificationThreadStopCount
++;
845 if (_notificationThreadStopCount
== 1) // If this call was the first to increment.
848 _notificationAutoResetEvent
.WaitOne();
853 internal void ResumeNotificationThread()
855 _notificationThreadStopCount
--;
856 if (_notificationThreadStopCount
== 0)
858 // Release the synchronization handle.
860 _notificationAutoResetEvent
.Set();
865 internal Boolean IsNotificationThreadRunning
869 return _notificationThreadStopCount
<= 0;
875 internal class NpgsqlContextHolder
878 private NpgsqlConnector connector
;
879 private NpgsqlState state
;
881 internal NpgsqlContextHolder(NpgsqlConnector connector
, NpgsqlState state
)
883 this.connector
= connector
;
888 internal void ProcessServerMessages()
893 this.connector
._notificationAutoResetEvent
.WaitOne();
895 if (this.connector
.Socket
.Poll(1000, SelectMode
.SelectRead
))
897 // reset any responses just before getting new ones
898 this.connector
.Mediator
.ResetResponses();
899 this.state
.ProcessBackendResponses(this.connector
);
900 this.connector
.CheckErrorsAndNotifications();
903 this.connector
._notificationAutoResetEvent
.Set();