1 // created on 10/5/2002 at 23:01
2 // Npgsql.NpgsqlConnection.cs
5 // Francisco Jr. (fxjrlists@yahoo.com.br)
7 // Copyright (C) 2002 The Npgsql Development Team
8 // npgsql-general@gborg.postgresql.org
9 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
12 // Permission to use, copy, modify, and distribute this software and its
13 // documentation for any purpose, without fee, and without a written
14 // agreement is hereby granted, provided that the above copyright notice
15 // and this paragraph and the following two paragraphs appear in all copies.
17 // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
18 // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
19 // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
20 // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
21 // THE POSSIBILITY OF SUCH DAMAGE.
23 // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
24 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25 // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
26 // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
27 // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
30 using System.ComponentModel;
32 using System.Data.Common;
33 using System.Resources;
34 using System.Security.Cryptography;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Transactions;
37 using Mono.Security.Protocol.Tls;
38 using IsolationLevel = System.Data.IsolationLevel;
47 /// Represents the method that handles the <see cref="Npgsql.NpgsqlConnection.Notification">Notice</see> events.
49 /// <param name="e">A <see cref="Npgsql.NpgsqlNoticeEventArgs">NpgsqlNoticeEventArgs</see> that contains the event data.</param>
50 public delegate void NoticeEventHandler(Object sender, NpgsqlNoticeEventArgs e);
53 /// Represents the method that handles the <see cref="Npgsql.NpgsqlConnection.Notification">Notification</see> events.
55 /// <param name="sender">The source of the event.</param>
56 /// <param name="e">A <see cref="Npgsql.NpgsqlNotificationEventArgs">NpgsqlNotificationEventArgs</see> that contains the event data.</param>
57 public delegate void NotificationEventHandler(Object sender, NpgsqlNotificationEventArgs e);
60 /// This class represents a connection to a
61 /// PostgreSQL server.
64 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlConnection))]
67 public sealed class NpgsqlConnection : DbConnection, ICloneable
69 // Logging related values
70 private static readonly String CLASSNAME = "NpgsqlConnection";
71 private static readonly ResourceManager resman = new ResourceManager(typeof(NpgsqlConnection));
73 // Parsed connection string cache
74 private static readonly Cache<NpgsqlConnectionStringBuilder> cache = new Cache<NpgsqlConnectionStringBuilder>();
77 /// Occurs on NoticeResponses from the PostgreSQL backend.
79 public event NoticeEventHandler Notice;
81 internal NoticeEventHandler NoticeDelegate;
84 /// Occurs on NotificationResponses from the PostgreSQL backend.
86 public event NotificationEventHandler Notification;
88 internal NotificationEventHandler NotificationDelegate;
91 /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
93 public event CertificateSelectionCallback CertificateSelectionCallback;
95 internal CertificateSelectionCallback CertificateSelectionCallbackDelegate;
98 /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
100 public event CertificateValidationCallback CertificateValidationCallback;
102 internal CertificateValidationCallback CertificateValidationCallbackDelegate;
105 /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
107 public event PrivateKeySelectionCallback PrivateKeySelectionCallback;
109 internal PrivateKeySelectionCallback PrivateKeySelectionCallbackDelegate;
111 // Set this when disposed is called.
112 private bool disposed = false;
114 // Used when we closed the connector due to an error, but are pretending it's open.
115 private bool _fakingOpen;
117 // Strong-typed ConnectionString values
118 private NpgsqlConnectionStringBuilder settings;
120 // Connector being used for the active connection.
121 private NpgsqlConnector connector = null;
123 private readonly NpgsqlPromotableSinglePhaseNotification promotable = null;
126 /// Initializes a new instance of the
127 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class.
129 public NpgsqlConnection()
135 /// Initializes a new instance of the
136 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class
137 /// and sets the <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
139 /// <param name="ConnectionString">The connection used to open the PostgreSQL database.</param>
140 public NpgsqlConnection(String ConnectionString)
142 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME, "NpgsqlConnection()");
144 NpgsqlConnectionStringBuilder builder = cache[ConnectionString];
147 settings = new NpgsqlConnectionStringBuilder(ConnectionString);
151 settings = builder.Clone();
154 LogConnectionString();
156 NoticeDelegate = new NoticeEventHandler(OnNotice);
157 NotificationDelegate = new NotificationEventHandler(OnNotification);
159 CertificateValidationCallbackDelegate = new CertificateValidationCallback(DefaultCertificateValidationCallback);
160 CertificateSelectionCallbackDelegate = new CertificateSelectionCallback(DefaultCertificateSelectionCallback);
161 PrivateKeySelectionCallbackDelegate = new PrivateKeySelectionCallback(DefaultPrivateKeySelectionCallback);
163 // Fix authentication problems. See https://bugzilla.novell.com/show_bug.cgi?id=MONO77559 and
164 // http://pgfoundry.org/forum/message.php?msg_id=1002377 for more info.
165 RSACryptoServiceProvider.UseMachineKeyStore = true;
167 promotable = new NpgsqlPromotableSinglePhaseNotification(this);
171 /// Gets or sets the string used to connect to a PostgreSQL database.
172 /// Valid values are:
175 /// Server: Address/Name of Postgresql Server;
178 /// Port: Port to connect to;
181 /// Protocol: Protocol version to use, instead of automatic; Integer 2 or 3;
184 /// Database: Database name. Defaults to user name if not specified;
187 /// User Id: User name;
190 /// Password: Password for clear text authentication;
193 /// SSL: True or False. Controls whether to attempt a secure connection. Default = False;
196 /// Pooling: True or False. Controls whether connection pooling is used. Default = True;
199 /// MinPoolSize: Min size of connection pool;
202 /// MaxPoolSize: Max size of connection pool;
205 /// Timeout: Time to wait for connection open in seconds. Default is 15.
208 /// CommandTimeout: Time to wait for command to finish execution before throw an exception. In seconds. Default is 20.
211 /// Sslmode: Mode for ssl connection control. Can be Prefer, Require, Allow or Disable. Default is Disable. Check user manual for explanation of values.
214 /// ConnectionLifeTime: Time to wait before closing unused connections in the pool in seconds. Default is 15.
217 /// SyncNotification: Specifies if Npgsql should use synchronous notifications.
220 /// SearchPath: Changes search path to specified and public schemas.
224 /// <value>The connection string that includes the server name,
225 /// the database name, and other parameters needed to establish
226 /// the initial connection. The default value is an empty string.
230 [RefreshProperties(RefreshProperties.All), DefaultValue(""), RecommendedAsConfigurable(true)]
231 [NpgsqlSysDescription("Description_ConnectionString", typeof(NpgsqlConnection)), Category("Data")]
232 [Editor(typeof(ConnectionStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
235 public override String ConnectionString
237 get { return settings.ConnectionString; }
240 // Connection string is used as the key to the connector. Because of this,
241 // we cannot change it while we own a connector.
242 CheckConnectionClosed();
243 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "ConnectionString", value);
244 NpgsqlConnectionStringBuilder builder = cache[value];
247 settings = new NpgsqlConnectionStringBuilder(value);
251 settings = builder.Clone();
253 LogConnectionString();
258 /// Backend server host name.
263 get { return settings.Host; }
267 /// Backend server port.
272 get { return settings.Port; }
276 /// If true, the connection will attempt to use SSL.
281 get { return settings.SSL; }
285 /// Gets the time to wait while trying to establish a connection
286 /// before terminating the attempt and generating an error.
288 /// <value>The time (in seconds) to wait for a connection to open. The default value is 15 seconds.</value>
291 [NpgsqlSysDescription("Description_ConnectionTimeout", typeof(NpgsqlConnection))]
294 public override Int32 ConnectionTimeout
296 get { return settings.Timeout; }
300 /// Gets the time to wait while trying to execute a command
301 /// before terminating the attempt and generating an error.
303 /// <value>The time (in seconds) to wait for a command to complete. The default value is 20 seconds.</value>
304 public Int32 CommandTimeout
306 get { return settings.CommandTimeout; }
310 /// Gets the time to wait before closing unused connections in the pool if the count
311 /// of all connections exeeds MinPoolSize.
314 /// If connection pool contains unused connections for ConnectionLifeTime seconds,
315 /// the half of them will be closed. If there will be unused connections in a second
316 /// later then again the half of them will be closed and so on.
317 /// This strategy provide smooth change of connection count in the pool.
319 /// <value>The time (in seconds) to wait. The default value is 15 seconds.</value>
320 public Int32 ConnectionLifeTime
322 get { return settings.ConnectionLifeTime; }
326 /// Gets the name of the current database or the database to be used after a connection is opened.
328 /// <value>The name of the current database or the name of the database to be
329 /// used after a connection is opened. The default value is the empty string.</value>
331 [NpgsqlSysDescription("Description_Database", typeof(NpgsqlConnection))]
334 public override String Database
336 get { return settings.Database; }
340 /// Whether datareaders are loaded in their entirety (for compatibility with earlier code).
342 public bool PreloadReader
344 get { return settings.PreloadReader; }
348 /// Gets the database server name.
350 public override string DataSource
352 get { return settings.Host; }
356 /// Gets flag indicating if we are using Synchronous notification or not.
357 /// The default value is false.
359 public Boolean SyncNotification
361 get { return settings.SyncNotification; }
365 /// Gets the current state of the connection.
367 /// <value>A bitwise combination of the <see cref="System.Data.ConnectionState">ConnectionState</see> values. The default is <b>Closed</b>.</value>
369 public ConnectionState FullState
375 if (connector != null)
377 return connector.State;
381 return ConnectionState.Closed;
387 /// Gets whether the current state of the connection is Open or Closed
389 /// <value>ConnectionState.Open or ConnectionState.Closed</value>
391 public override ConnectionState State
395 return (FullState & ConnectionState.Open) == ConnectionState.Open ? ConnectionState.Open : ConnectionState.Closed;
400 /// Version of the PostgreSQL backend.
401 /// This can only be called when there is an active connection.
404 public Version PostgreSqlVersion
408 CheckConnectionOpen();
409 return connector.ServerVersion;
413 public override string ServerVersion
415 get { return PostgreSqlVersion.ToString(); }
419 /// Protocol version in use.
420 /// This can only be called when there is an active connection.
423 public ProtocolVersion BackendProtocolVersion
427 CheckConnectionOpen();
428 return connector.BackendProtocolVersion;
433 /// Process id of backend server.
434 /// This can only be called when there is an active connection.
437 public Int32 ProcessID
441 CheckConnectionOpen();
442 return connector.BackEndKeyData.ProcessID;
447 /// Begins a database transaction with the specified isolation level.
449 /// <param name="isolationLevel">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
450 /// <returns>An <see cref="System.Data.Common.DbTransaction">DbTransaction</see>
451 /// object representing the new transaction.</returns>
453 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
454 /// There's no support for nested transactions.
456 protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
458 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginDbTransaction", isolationLevel);
460 return BeginTransaction(isolationLevel);
464 /// Begins a database transaction.
466 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
467 /// object representing the new transaction.</returns>
469 /// Currently there's no support for nested transactions.
471 public new NpgsqlTransaction BeginTransaction()
473 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction");
474 return this.BeginTransaction(IsolationLevel.ReadCommitted);
478 /// Begins a database transaction with the specified isolation level.
480 /// <param name="level">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
481 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
482 /// object representing the new transaction.</returns>
484 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
485 /// There's no support for nested transactions.
487 public new NpgsqlTransaction BeginTransaction(IsolationLevel level)
489 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction", level);
491 CheckConnectionOpen();
493 if (connector.Transaction != null)
495 throw new InvalidOperationException(resman.GetString("Exception_NoNestedTransactions"));
498 return new NpgsqlTransaction(this, level);
502 /// Opens a database connection with the property settings specified by the
503 /// <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
505 public override void Open()
507 CheckConnectionClosed();
509 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
511 // Check if there is any missing argument.
512 if (!settings.ContainsKey(Keywords.Host))
514 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"),
515 NpgsqlConnectionStringBuilder.GetKeyName(Keywords.Host));
517 if (!settings.ContainsKey(Keywords.UserName) && !settings.ContainsKey(Keywords.IntegratedSecurity))
519 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"),
520 NpgsqlConnectionStringBuilder.GetKeyName(Keywords.UserName));
523 // Get a Connector. The connector returned is guaranteed to be connected and ready to go.
524 connector = NpgsqlConnectorPool.ConnectorPoolMgr.RequestConnector(this);
526 connector.Notice += NoticeDelegate;
527 connector.Notification += NotificationDelegate;
529 if (SyncNotification)
531 connector.AddNotificationThread();
536 promotable.Enlist(Transaction.Current);
541 /// This method changes the current database by disconnecting from the actual
542 /// database and connecting to the specified.
544 /// <param name="dbName">The name of the database to use in place of the current database.</param>
545 public override void ChangeDatabase(String dbName)
549 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeDatabase", dbName);
553 throw new ArgumentNullException("dbName");
556 if (string.IsNullOrEmpty(dbName))
558 throw new ArgumentOutOfRangeException(String.Format(resman.GetString("Exception_InvalidDbName"), dbName), "dbName");
561 String oldDatabaseName = Database;
565 settings[Keywords.Database] = dbName;
570 internal void EmergencyClose()
576 /// Releases the connection to the database. If the connection is pooled, it will be
577 /// made available for re-use. If it is non-pooled, the actual connection will be shutdown.
579 public override void Close()
583 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
585 promotable.Prepare();
587 if (connector != null)
589 connector.Notification -= NotificationDelegate;
590 connector.Notice -= NoticeDelegate;
592 if (SyncNotification)
594 connector.RemoveNotificationThread();
597 NpgsqlConnectorPool.ConnectorPoolMgr.ReleaseConnector(this, connector);
606 /// Creates and returns a <see cref="System.Data.Common.DbCommand">DbCommand</see>
607 /// object associated with the <see cref="System.Data.Common.DbConnection">IDbConnection</see>.
609 /// <returns>A <see cref="System.Data.Common.DbCommand">DbCommand</see> object.</returns>
610 protected override DbCommand CreateDbCommand()
612 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateDbCommand");
613 return CreateCommand();
617 /// Creates and returns a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>
618 /// object associated with the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
620 /// <returns>A <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> object.</returns>
621 public new NpgsqlCommand CreateCommand()
625 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateCommand");
626 return new NpgsqlCommand("", this);
630 /// Releases all resources used by the
631 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
633 /// <param name="disposing"><b>true</b> when called from Dispose();
634 /// <b>false</b> when being called from the finalizer.</param>
635 protected override void Dispose(bool disposing)
641 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
646 if (FullState != ConnectionState.Closed)
648 NpgsqlEventLog.LogMsg(resman, "Log_ConnectionLeaking", LogLevel.Debug);
649 NpgsqlConnectorPool.ConnectorPoolMgr.FixPoolCountBecauseOfConnectionDisposeFalse(this);
653 base.Dispose(disposing);
659 /// Create a new connection based on this one.
661 /// <returns>A new NpgsqlConnection object.</returns>
662 Object ICloneable.Clone()
668 /// Create a new connection based on this one.
670 /// <returns>A new NpgsqlConnection object.</returns>
671 public NpgsqlConnection Clone()
675 NpgsqlConnection C = new NpgsqlConnection(ConnectionString);
677 C.Notice += this.Notice;
679 if (connector != null)
688 // Internal methods and properties
690 internal void OnNotice(object O, NpgsqlNoticeEventArgs E)
698 internal void OnNotification(object O, NpgsqlNotificationEventArgs E)
700 if (Notification != null)
702 Notification(this, E);
707 /// The connector object connected to the backend.
709 internal NpgsqlConnector Connector
711 get { return connector; }
716 /// Gets the NpgsqlConnectionStringBuilder containing the parsed connection string values.
718 internal NpgsqlConnectionStringBuilder ConnectionStringValues
720 get { return settings; }
726 internal String UserName
728 get { return settings.UserName; }
731 public bool UseExtendedTypes
735 bool ext = settings.UseExtendedTypes;
743 internal String Password
745 get { return settings.Password; }
749 /// Determine if connection pooling will be used for this connection.
751 internal Boolean Pooling
753 get { return (settings.Pooling && (settings.MaxPoolSize > 0)); }
756 internal Int32 MinPoolSize
758 get { return settings.MinPoolSize; }
761 internal Int32 MaxPoolSize
763 get { return settings.MaxPoolSize; }
766 internal Int32 Timeout
768 get { return settings.Timeout; }
771 internal Boolean Enlist
773 get { return settings.Enlist; }
782 /// Default SSL CertificateSelectionCallback implementation.
784 internal X509Certificate DefaultCertificateSelectionCallback(X509CertificateCollection clientCertificates,
785 X509Certificate serverCertificate, string targetHost,
786 X509CertificateCollection serverRequestedCertificates)
788 if (CertificateSelectionCallback != null)
790 return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
799 /// Default SSL CertificateValidationCallback implementation.
801 internal bool DefaultCertificateValidationCallback(X509Certificate certificate, int[] certificateErrors)
803 if (CertificateValidationCallback != null)
805 return CertificateValidationCallback(certificate, certificateErrors);
814 /// Default SSL PrivateKeySelectionCallback implementation.
816 internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(X509Certificate certificate, string targetHost)
818 if (PrivateKeySelectionCallback != null)
820 return PrivateKeySelectionCallback(certificate, targetHost);
830 // Private methods and properties
835 /// Write each key/value pair in the connection string to the log.
837 private void LogConnectionString()
839 foreach (string key in settings.Keys)
841 NpgsqlEventLog.LogMsg(resman, "Log_ConnectionStringValues", LogLevel.Debug, key, settings[key]);
845 private void CheckConnectionOpen()
849 throw new ObjectDisposedException(CLASSNAME);
854 if (connector != null)
868 if (connector == null)
870 throw new InvalidOperationException(resman.GetString("Exception_ConnNotOpen"));
874 private void CheckConnectionClosed()
878 throw new ObjectDisposedException(CLASSNAME);
881 if (connector != null)
883 throw new InvalidOperationException(resman.GetString("Exception_ConnOpen"));
887 private void CheckNotDisposed()
891 throw new ObjectDisposedException(CLASSNAME);
897 /// Returns the supported collections
899 public override DataTable GetSchema()
901 return NpgsqlSchema.GetMetaDataCollections();
905 /// Returns the schema collection specified by the collection name.
907 /// <param name="collectionName">The collection name.</param>
908 /// <returns>The collection specified.</returns>
909 public override DataTable GetSchema(string collectionName)
911 return GetSchema(collectionName, null);
915 /// Returns the schema collection specified by the collection name filtered by the restrictions.
917 /// <param name="collectionName">The collection name.</param>
918 /// <param name="restrictions">
919 /// The restriction values to filter the results. A description of the restrictions is contained
920 /// in the Restrictions collection.
922 /// <returns>The collection specified.</returns>
923 public override DataTable GetSchema(string collectionName, string[] restrictions)
925 switch (collectionName)
927 case "MetaDataCollections":
928 return NpgsqlSchema.GetMetaDataCollections();
930 return NpgsqlSchema.GetRestrictions();
931 case "DataSourceInformation":
932 return NpgsqlSchema.GetDataSourceInformation();
934 case "ReservedWords":
935 throw new NotSupportedException();
936 // custom collections for npgsql
938 return new NpgsqlSchema(new NpgsqlConnection(ConnectionString)).GetDatabases(restrictions);
940 return new NpgsqlSchema(new NpgsqlConnection(ConnectionString)).GetTables(restrictions);
942 return new NpgsqlSchema(new NpgsqlConnection(ConnectionString)).GetColumns(restrictions);
944 return new NpgsqlSchema(new NpgsqlConnection(ConnectionString)).GetViews(restrictions);
946 return new NpgsqlSchema(new NpgsqlConnection(ConnectionString)).GetUsers(restrictions);
948 throw new NotSupportedException();
952 public void ClearPool()
954 NpgsqlConnectorPool.ConnectorPoolMgr.ClearPool(this);
957 public static void ClearAllPools()
959 NpgsqlConnectorPool.ConnectorPoolMgr.ClearAllPools();
962 public override void EnlistTransaction(Transaction transaction)
964 promotable.Enlist(transaction);
968 protected override DbProviderFactory DbProviderFactory
972 return NpgsqlFactory.Instance;