disable broken tests on net_4_0
[mcs.git] / class / Npgsql / Npgsql / NpgsqlConnector.cs
blobfd6c391b0aace47eb5924a5b16609753a34a5a5d
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.
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
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
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 using System.Threading;
36 using System.Net.Sockets;
38 using Mono.Security.Protocol.Tls;
40 using NpgsqlTypes;
42 namespace Npgsql
44 /// <summary>
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.
49 /// </summary>
50 internal class NpgsqlConnector
52 // Immutable.
53 internal NpgsqlConnectionString ConnectionString;
55 /// <summary>
56 /// Occurs on NoticeResponses from the PostgreSQL backend.
57 /// </summary>
58 internal event NoticeEventHandler Notice;
60 /// <summary>
61 /// Occurs on NotificationResponses from the PostgreSQL backend.
62 /// </summary>
63 internal event NotificationEventHandler Notification;
65 /// <summary>
66 /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
67 /// </summary>
68 internal event CertificateSelectionCallback CertificateSelectionCallback;
70 /// <summary>
71 /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
72 /// </summary>
73 internal event CertificateValidationCallback CertificateValidationCallback;
75 /// <summary>
76 /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
77 /// </summary>
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;
131 /// <summary>
132 /// Constructor.
133 /// </summary>
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;
139 _pooled = Pooled;
140 _shared = Shared;
141 _isInitialized = false;
142 _state = NpgsqlClosedState.Instance;
143 _mediator = new NpgsqlMediator();
144 _oidToNameMapping = new NpgsqlBackendTypeMapping();
145 _planIndex = 0;
146 _portalIndex = 0;
147 _notificationThreadStopCount = 1;
148 _notificationAutoResetEvent = new AutoResetEvent(true);
153 internal String Host
157 return ConnectionString.ToString(ConnectionStringKeys.Host);
161 internal Int32 Port
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);
193 internal Boolean SSL
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);
226 /// <summary>
227 /// Gets the current state of the connection.
228 /// </summary>
229 internal ConnectionState State {
232 return _connection_state;
237 // 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);
280 /// <summary>
281 /// This method checks if the connector is still ok.
282 /// We try to send a simple query text, select 1 as ConnectionTest;
283 /// </summary>
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));
293 // Clear mediator.
294 Mediator.ResetResponses();
295 Mediator.ResetExpectations();
299 catch
301 return false;
304 return true;
309 /// <summary>
310 /// This method is responsible for releasing all resources associated with this Connector.
311 /// </summary>
313 internal void ReleaseResources()
315 ReleasePlansPortals();
316 ReleaseRegisteredListen();
321 internal void ReleaseRegisteredListen()
323 Query(new NpgsqlCommand("unlisten *", this));
327 /// <summary>
328 /// This method is responsible to release all portals used by this Connector.
329 /// </summary>
330 internal void ReleasePlansPortals()
332 Int32 i = 0;
334 if (_planIndex > 0)
336 for(i = 1; i <= _planIndex; i++)
337 Query(new NpgsqlCommand(String.Format("deallocate \"{0}\";", _planNamePrefix + i), this));
340 _portalIndex = 0;
341 _planIndex = 0;
350 /// <summary>
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.
354 /// </summary>
355 internal void CheckErrors()
357 if (_mediator.Errors.Count > 0)
359 throw new NpgsqlException(_mediator.Errors);
363 /// <summary>
364 /// Check for notices and fire the appropiate events.
365 /// This needs to be called after every interaction
366 /// with the backend.
367 /// </summary>
368 internal void CheckNotices()
370 if (Notice != null)
372 foreach (NpgsqlError E in _mediator.Notices)
374 Notice(this, new NpgsqlNoticeEventArgs(E));
379 /// <summary>
380 /// Check for notifications and fire the appropiate events.
381 /// This needs to be called after every interaction
382 /// with the backend.
383 /// </summary>
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);
397 catch(Exception){}
403 /// <summary>
404 /// Check for errors AND notifications in one call.
405 /// </summary>
406 internal void CheckErrorsAndNotifications()
408 CheckNotices();
409 CheckNotifications();
410 CheckErrors();
413 /// <summary>
414 /// Default SSL CertificateSelectionCallback implementation.
415 /// </summary>
416 internal X509Certificate DefaultCertificateSelectionCallback(
417 X509CertificateCollection clientCertificates,
418 X509Certificate serverCertificate,
419 string targetHost,
420 X509CertificateCollection serverRequestedCertificates)
422 if (CertificateSelectionCallback != null)
424 return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
426 else
428 return null;
432 /// <summary>
433 /// Default SSL CertificateValidationCallback implementation.
434 /// </summary>
435 internal bool DefaultCertificateValidationCallback(
436 X509Certificate certificate,
437 int[] certificateErrors)
439 if (CertificateValidationCallback != null)
441 return CertificateValidationCallback(certificate, certificateErrors);
443 else
445 return true;
449 /// <summary>
450 /// Default SSL PrivateKeySelectionCallback implementation.
451 /// </summary>
452 internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(
453 X509Certificate certificate,
454 string targetHost)
456 if (PrivateKeySelectionCallback != null)
458 return PrivateKeySelectionCallback(certificate, targetHost);
460 else
462 return null;
466 /// <summary>
467 /// Version of backend server this connector is connected to.
468 /// </summary>
469 internal ServerVersion ServerVersion
473 return _serverVersion;
477 _serverVersion = value;
481 internal Encoding Encoding
485 return _encoding;
489 _encoding = value;
493 /// <summary>
494 /// Backend protocol version in use by this connector.
495 /// </summary>
496 internal ProtocolVersion BackendProtocolVersion
500 return _backendProtocolVersion;
504 _backendProtocolVersion = value;
508 /// <summary>
509 /// The physical connection stream to the backend.
510 /// </summary>
511 internal Stream Stream {
514 return _stream;
518 _stream = value;
522 /// <summary>
523 /// The physical connection socket to the backend.
524 /// </summary>
526 internal Socket Socket {
529 return _socket;
533 _socket = value;
537 /// <summary>
538 /// Reports if this connector is fully connected.
539 /// </summary>
540 internal Boolean IsInitialized
544 return _isInitialized;
548 _isInitialized = value;
552 internal NpgsqlState CurrentState {
555 return _state;
559 _state = value;
564 internal bool Pooled
568 return _pooled;
572 internal bool Shared
576 return _shared;
580 internal NpgsqlBackEndKeyData BackEndKeyData {
583 return _backend_keydata;
587 internal NpgsqlBackendTypeMapping OidToNameMapping {
590 return _oidToNameMapping;
594 /// <summary>
595 /// The connection mediator.
596 /// </summary>
597 internal NpgsqlMediator Mediator {
600 return _mediator;
604 /// <summary>
605 /// Report if the connection is in a transaction.
606 /// </summary>
607 internal NpgsqlTransaction Transaction {
610 return _transaction;
614 _transaction = value;
618 /// <summary>
619 /// Report whether the current connection can support prepare functionality.
620 /// </summary>
621 internal Boolean SupportsPrepare {
624 return _supportsPrepare;
628 _supportsPrepare = value;
632 /// <summary>
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+)
635 /// </summary>
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;
647 /// <summary>
648 /// Opens the physical connection to the server.
649 /// </summary>
650 /// <remarks>Usually called by the RequestConnector
651 /// Method of the connection pool manager.</remarks>
652 internal void Open()
654 ProtocolVersion PV;
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);
662 else
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
707 CheckErrors();
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
731 // backend version.
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;
760 /// <summary>
761 /// Closes the physical connection to the server.
762 /// </summary>
763 internal void Close()
767 this.CurrentState.Close(this);
769 catch {}
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);
790 ///<summary>
791 /// Returns next portal index.
792 ///</summary>
793 internal String NextPortalName()
796 return _portalNamePrefix + System.Threading.Interlocked.Increment(ref _portalIndex);
800 ///<summary>
801 /// Returns next plan index.
802 ///</summary>
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;
884 this.state = state;
888 internal void ProcessServerMessages()
891 while(true)
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();