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;
36 using Mono.Security.Protocol.Tls;
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.
48 internal class NpgsqlConnector
51 internal NpgsqlConnectionString ConnectionString;
54 /// Occurs on NoticeResponses from the PostgreSQL backend.
56 internal event NoticeEventHandler Notice;
59 /// Occurs on NotificationResponses from the PostgreSQL backend.
61 internal event NotificationEventHandler Notification;
64 /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
66 internal event CertificateSelectionCallback CertificateSelectionCallback;
69 /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
71 internal event CertificateValidationCallback CertificateValidationCallback;
74 /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
76 internal event PrivateKeySelectionCallback PrivateKeySelectionCallback;
78 private ConnectionState _connection_state;
80 // The physical network connection to the backend.
81 private Stream _stream;
83 // Mediator which will hold data generated from backend.
84 private NpgsqlMediator _mediator;
86 private ProtocolVersion _backendProtocolVersion;
87 private ServerVersion _serverVersion;
89 // Values for possible CancelRequest messages.
90 private NpgsqlBackEndKeyData _backend_keydata;
92 // Flag for transaction status.
93 // private Boolean _inTransaction = false;
94 private NpgsqlTransaction _transaction = null;
96 private Boolean _supportsPrepare = false;
98 private NpgsqlBackendTypeMapping _oidToNameMapping = null;
100 private Encoding _encoding;
102 private Boolean _isInitialized;
104 private Boolean _pooled;
105 private Boolean _shared;
107 private NpgsqlState _state;
110 private Int32 _planIndex;
111 private Int32 _portalIndex;
113 private const String _planNamePrefix = "npgsqlplan";
114 private const String _portalNamePrefix = "npgsqlportal";
121 /// <param name="Shared">Controls whether the connector can be shared.</param>
122 public NpgsqlConnector(NpgsqlConnectionString ConnectionString, bool Pooled, bool Shared)
124 this.ConnectionString = ConnectionString;
125 _connection_state = ConnectionState.Closed;
128 _isInitialized = false;
129 _state = NpgsqlClosedState.Instance;
130 _mediator = new NpgsqlMediator();
131 _oidToNameMapping = new NpgsqlBackendTypeMapping();
142 return ConnectionString.ToString(ConnectionStringKeys.Host);
150 return ConnectionString.ToInt32(ConnectionStringKeys.Port, ConnectionStringDefaults.Port);
154 internal String Database
158 return ConnectionString.ToString(ConnectionStringKeys.Database, UserName);
162 internal String UserName
166 return ConnectionString.ToString(ConnectionStringKeys.UserName);
170 internal String Password
174 return ConnectionString.ToString(ConnectionStringKeys.Password);
182 return ConnectionString.ToBool(ConnectionStringKeys.SSL);
186 internal SslMode SslMode
190 return ConnectionString.ToSslMode(ConnectionStringKeys.SslMode);
197 /// Gets the current state of the connection.
199 internal ConnectionState State {
202 return _connection_state;
208 internal void Query (NpgsqlCommand queryCommand)
210 CurrentState.Query(this, queryCommand );
213 internal void Authenticate (string password)
215 CurrentState.Authenticate(this, password );
218 internal void Parse (NpgsqlParse parse)
220 CurrentState.Parse(this, parse);
223 internal void Flush ()
225 CurrentState.Flush(this);
228 internal void Sync ()
230 CurrentState.Sync(this);
233 internal void Bind (NpgsqlBind bind)
235 CurrentState.Bind(this, bind);
238 internal void Execute (NpgsqlExecute execute)
240 CurrentState.Execute(this, execute);
246 /// This method checks if the connector is still ok.
247 /// We try to send a simple query text, select 1 as ConnectionTest;
250 internal Boolean IsValid()
254 // Here we use a fake NpgsqlCommand, just to send the test query string.
255 Query(new NpgsqlCommand("select 1 as ConnectionTest"));
258 Mediator.ResetResponses();
259 Mediator.ResetExpectations();
273 /// This method is responsible to release all portals used by this Connector.
275 internal void ReleasePlansPortals()
281 for(i = 1; i <= _planIndex; i++)
282 Query(new NpgsqlCommand(String.Format("deallocate \"{0}\";", _planNamePrefix + i)));
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.
298 internal void CheckErrors()
300 if (_mediator.Errors.Count > 0)
302 throw new NpgsqlException(_mediator.Errors);
307 /// Check for notices and fire the appropiate events.
308 /// This needs to be called after every interaction
309 /// with the backend.
311 internal void CheckNotices()
315 foreach (NpgsqlError E in _mediator.Notices)
317 Notice(this, new NpgsqlNoticeEventArgs(E));
323 /// Check for notifications and fire the appropiate events.
324 /// This needs to be called after every interaction
325 /// with the backend.
327 internal void CheckNotifications()
329 if (Notification != null)
331 foreach (NpgsqlNotificationEventArgs E in _mediator.Notifications)
333 Notification(this, E);
339 /// Check for errors AND notifications in one call.
341 internal void CheckErrorsAndNotifications()
344 CheckNotifications();
349 /// Default SSL CertificateSelectionCallback implementation.
351 internal X509Certificate DefaultCertificateSelectionCallback(
352 X509CertificateCollection clientCertificates,
353 X509Certificate serverCertificate,
355 X509CertificateCollection serverRequestedCertificates)
357 if (CertificateSelectionCallback != null)
359 return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
368 /// Default SSL CertificateValidationCallback implementation.
370 internal bool DefaultCertificateValidationCallback(
371 X509Certificate certificate,
372 int[] certificateErrors)
374 if (CertificateValidationCallback != null)
376 return CertificateValidationCallback(certificate, certificateErrors);
385 /// Default SSL PrivateKeySelectionCallback implementation.
387 internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(
388 X509Certificate certificate,
391 if (PrivateKeySelectionCallback != null)
393 return PrivateKeySelectionCallback(certificate, targetHost);
402 /// Version of backend server this connector is connected to.
404 internal ServerVersion ServerVersion
408 return _serverVersion;
412 _serverVersion = value;
416 internal Encoding Encoding
429 /// Backend protocol version in use by this connector.
431 internal ProtocolVersion BackendProtocolVersion
435 return _backendProtocolVersion;
439 _backendProtocolVersion = value;
444 /// The physical connection stream to the backend.
446 internal Stream Stream {
458 /// Reports if this connector is fully connected.
460 internal Boolean IsInitialized
464 return _isInitialized;
468 _isInitialized = value;
472 internal NpgsqlState CurrentState {
500 internal NpgsqlBackEndKeyData BackEndKeyData {
503 return _backend_keydata;
507 internal NpgsqlBackendTypeMapping OidToNameMapping {
510 return _oidToNameMapping;
515 /// The connection mediator.
517 internal NpgsqlMediator Mediator {
525 /// Report if the connection is in a transaction.
527 internal NpgsqlTransaction Transaction {
534 _transaction = value;
539 /// Report whether the current connection can support prepare functionality.
541 internal Boolean SupportsPrepare {
544 return _supportsPrepare;
548 _supportsPrepare = value;
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+)
556 // FIXME - should be private
557 internal void ProcessServerVersion ()
559 this._supportsPrepare = (ServerVersion >= new ServerVersion(7, 3, 0));
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;
568 /// Opens the physical connection to the server.
570 /// <remarks>Usually called by the RequestConnector
571 /// Method of the connection pool manager.</remarks>
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))
580 PV = ConnectionString.ToProtocolVersion(ConnectionStringKeys.Protocol);
584 PV = ProtocolVersion.Unknown;
587 _backendProtocolVersion = (PV == ProtocolVersion.Unknown) ? ProtocolVersion.Version3 : PV;
589 // Reset state to initialize new connector in pool.
590 Encoding = Encoding.Default;
591 CurrentState = NpgsqlClosedState.Instance;
593 // Get a raw connection, possibly SSL...
594 CurrentState.Open(this);
595 // Establish protocol communication and handle authentication...
596 CurrentState.Startup(this);
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)
602 // If we attempted protocol version 3, it may be possible to drop back to version 2.
603 if (BackendProtocolVersion == ProtocolVersion.Version3)
605 NpgsqlError Error0 = (NpgsqlError)_mediator.Errors[0];
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)
612 // Try using the 2.0 protocol.
613 _mediator.ResetResponses();
614 BackendProtocolVersion = ProtocolVersion.Version2;
615 CurrentState = NpgsqlClosedState.Instance;
617 // Get a raw connection, possibly SSL...
618 CurrentState.Open(this);
619 // Establish protocol communication and handle authentication...
620 CurrentState.Startup(this);
625 // Check for errors and do the Right Thing.
626 // FIXME - CheckErrors needs to be moved to Connector
629 _backend_keydata = _mediator.BackendKeyData;
631 // Change the state of connection to open and ready.
632 _connection_state = ConnectionState.Open;
633 CurrentState = NpgsqlReadyState.Instance;
635 String ServerVersionString = String.Empty;
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;
642 // Fall back to the old way, SELECT VERSION().
643 // This should not happen for protocol version 3+.
644 if (ServerVersionString.Length == 0)
646 NpgsqlCommand command = new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
647 ServerVersionString = PGUtil.ExtractServerVersion( (String)command.ExecuteScalar() );
650 // Cook version string so we can use it for enabling/disabling things based on
652 ServerVersion = PGUtil.ParseServerVersion(ServerVersionString);
654 // Adjust client encoding.
656 //NpgsqlCommand commandEncoding1 = new NpgsqlCommand("show client_encoding", _connector);
657 //String clientEncoding1 = (String)commandEncoding1.ExecuteScalar();
659 if (ConnectionString.ToString(ConnectionStringKeys.Encoding, ConnectionStringDefaults.Encoding).ToUpper() == "UNICODE")
661 Encoding = Encoding.UTF8;
662 NpgsqlCommand commandEncoding = new NpgsqlCommand("SET CLIENT_ENCODING TO UNICODE", this);
663 commandEncoding.ExecuteNonQuery();
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();
672 ProcessServerVersion();
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;
681 /// Closes the physical connection to the server.
683 internal void Close()
687 this.CurrentState.Close(this);
694 /// Returns next portal index.
696 internal String NextPortalName()
699 return _portalNamePrefix + System.Threading.Interlocked.Increment(ref _portalIndex);
704 /// Returns next plan index.
706 internal String NextPlanName()
708 return _planNamePrefix + System.Threading.Interlocked.Increment(ref _planIndex);