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 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Lesser General Public
14 // License as published by the Free Software Foundation; either
15 // version 2.1 of the License, or (at your option) any later version.
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 // Lesser General Public License for more details.
22 // You should have received a copy of the GNU Lesser General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 using System.ComponentModel;
30 using System.Collections;
31 using System.Resources;
32 using System.Security.Cryptography;
33 using System.Security.Cryptography.X509Certificates;
35 using Mono.Security.Protocol.Tls;
46 /// Represents the method that handles the <see cref="Npgsql.NpgsqlConnection.Notification">Notice</see> events.
48 /// <param name="e">A <see cref="Npgsql.NpgsqlNoticeEventArgs">NpgsqlNoticeEventArgs</see> that contains the event data.</param>
49 public delegate void NoticeEventHandler(Object sender, NpgsqlNoticeEventArgs e);
52 /// Represents the method that handles the <see cref="Npgsql.NpgsqlConnection.Notification">Notification</see> events.
54 /// <param name="sender">The source of the event.</param>
55 /// <param name="e">A <see cref="Npgsql.NpgsqlNotificationEventArgs">NpgsqlNotificationEventArgs</see> that contains the event data.</param>
56 public delegate void NotificationEventHandler(Object sender, NpgsqlNotificationEventArgs e);
59 /// This class represents a connection to a
60 /// PostgreSQL server.
63 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlConnection))]
66 public sealed class NpgsqlConnection : Component, IDbConnection, ICloneable
68 // Logging related values
69 private static readonly String CLASSNAME = "NpgsqlConnection";
70 private static ResourceManager resman = new System.Resources.ResourceManager(typeof(NpgsqlConnection));
73 /// Occurs on NoticeResponses from the PostgreSQL backend.
75 public event NoticeEventHandler Notice;
76 internal NoticeEventHandler NoticeDelegate;
79 /// Occurs on NotificationResponses from the PostgreSQL backend.
81 public event NotificationEventHandler Notification;
82 internal NotificationEventHandler NotificationDelegate;
85 /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
87 public event CertificateSelectionCallback CertificateSelectionCallback;
88 internal CertificateSelectionCallback CertificateSelectionCallbackDelegate;
91 /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
93 public event CertificateValidationCallback CertificateValidationCallback;
94 internal CertificateValidationCallback CertificateValidationCallbackDelegate;
97 /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
99 public event PrivateKeySelectionCallback PrivateKeySelectionCallback;
100 internal PrivateKeySelectionCallback PrivateKeySelectionCallbackDelegate;
102 // Set this when disposed is called.
103 private bool disposed = false;
105 // Connection string values.
106 private NpgsqlConnectionString connection_string;
108 // Connector being used for the active connection.
109 private NpgsqlConnector connector = null;
113 /// Initializes a new instance of the
114 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class.
116 public NpgsqlConnection() : this(String.Empty)
120 /// Initializes a new instance of the
121 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class
122 /// and sets the <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
124 /// <param name="ConnectionString">The connection used to open the PostgreSQL database.</param>
125 public NpgsqlConnection(String ConnectionString)
127 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME, "NpgsqlConnection()");
129 connection_string = NpgsqlConnectionString.ParseConnectionString(ConnectionString);
130 LogConnectionString();
132 NoticeDelegate = new NoticeEventHandler(OnNotice);
133 NotificationDelegate = new NotificationEventHandler(OnNotification);
135 CertificateValidationCallbackDelegate = new CertificateValidationCallback(DefaultCertificateValidationCallback);
136 CertificateSelectionCallbackDelegate = new CertificateSelectionCallback(DefaultCertificateSelectionCallback);
137 PrivateKeySelectionCallbackDelegate = new PrivateKeySelectionCallback(DefaultPrivateKeySelectionCallback);
141 /// Gets or sets the string used to connect to a PostgreSQL database.
142 /// Valid values are:
143 /// Server: Address/Name of Postgresql Server;
144 /// Port: Port to connect to;
145 /// Protocol: Protocol version to use, instead of automatic; Integer 2 or 3;
146 /// Database: Database name. Defaults to user name if not specified;
148 /// Password: Password for clear text authentication;
149 /// SSL: True or False. Controls whether to attempt a secure connection. Default = False;
150 /// Pooling: True or False. Controls whether connection pooling is used. Default = True;
151 /// MinPoolSize: Min size of connection pool;
152 /// MaxPoolSize: Max size of connection pool;
153 /// Encoding: Encoding to be used;
154 /// Timeout: Time to wait for connection open in seconds.
156 /// <value>The connection string that includes the server name,
157 /// the database name, and other parameters needed to establish
158 /// the initial connection. The default value is an empty string.
162 [RefreshProperties(RefreshProperties.All), DefaultValue(""), RecommendedAsConfigurable(true)]
163 [NpgsqlSysDescription("Description_ConnectionString", typeof(NpgsqlConnection)), Category("Data")]
164 [Editor(typeof(ConnectionStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
167 public String ConnectionString {
170 return connection_string.ToString();
174 // Connection string is used as the key to the connector. Because of this,
175 // we cannot change it while we own a connector.
176 CheckConnectionClosed();
177 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "ConnectionString", value);
178 connection_string = NpgsqlConnectionString.ParseConnectionString(value);
179 LogConnectionString();
184 /// Backend server host name.
190 return connection_string.ToString(ConnectionStringKeys.Host);
195 /// Backend server port.
201 return connection_string.ToInt32(ConnectionStringKeys.Port, ConnectionStringDefaults.Port);
206 /// If true, the connection will attempt to use SSL.
212 return connection_string.ToBool(ConnectionStringKeys.SSL);
217 /// Gets the time to wait while trying to establish a connection
218 /// before terminating the attempt and generating an error.
220 /// <value>The time (in seconds) to wait for a connection to open. The default value is 15 seconds.</value>
223 [NpgsqlSysDescription("Description_ConnectionTimeout", typeof(NpgsqlConnection))]
226 public Int32 ConnectionTimeout {
229 return connection_string.ToInt32(ConnectionStringKeys.Timeout, ConnectionStringDefaults.Timeout);
234 /// Gets the name of the current database or the database to be used after a connection is opened.
236 /// <value>The name of the current database or the name of the database to be
237 /// used after a connection is opened. The default value is the empty string.</value>
239 [NpgsqlSysDescription("Description_Database", typeof(NpgsqlConnection))]
242 public String Database {
245 return connection_string.ToString(ConnectionStringKeys.Database);
250 /// Gets the current state of the connection.
252 /// <value>A bitwise combination of the <see cref="System.Data.ConnectionState">ConnectionState</see> values. The default is <b>Closed</b>.</value>
254 public ConnectionState State {
259 if (connector != null)
261 return connector.State;
265 return ConnectionState.Closed;
271 /// Version of the PostgreSQL backend.
272 /// This can only be called when there is an active connection.
275 public ServerVersion ServerVersion {
278 CheckConnectionOpen();
279 return connector.ServerVersion;
284 /// Protocol version in use.
285 /// This can only be called when there is an active connection.
288 public ProtocolVersion BackendProtocolVersion {
291 CheckConnectionOpen();
292 return connector.BackendProtocolVersion;
297 /// Begins a database transaction.
299 /// <returns>An <see cref="System.Data.IDbTransaction">IDbTransaction</see>
300 /// object representing the new transaction.</returns>
302 /// Currently there's no support for nested transactions.
304 IDbTransaction IDbConnection.BeginTransaction()
306 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.BeginTransaction");
308 return BeginTransaction();
312 /// Begins a database transaction with the specified isolation level.
314 /// <param name="level">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
315 /// <returns>An <see cref="System.Data.IDbTransaction">IDbTransaction</see>
316 /// object representing the new transaction.</returns>
318 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
319 /// There's no support for nested transactions.
321 IDbTransaction IDbConnection.BeginTransaction(IsolationLevel level)
323 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.BeginTransaction", level);
325 return BeginTransaction(level);
329 /// Begins a database transaction.
331 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
332 /// object representing the new transaction.</returns>
334 /// Currently there's no support for nested transactions.
336 public NpgsqlTransaction BeginTransaction()
338 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction");
339 return this.BeginTransaction(IsolationLevel.ReadCommitted);
343 /// Begins a database transaction with the specified isolation level.
345 /// <param name="level">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
346 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
347 /// object representing the new transaction.</returns>
349 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
350 /// There's no support for nested transactions.
352 public NpgsqlTransaction BeginTransaction(IsolationLevel level)
354 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction", level);
356 CheckConnectionOpen();
358 if (connector.Transaction != null)
360 throw new InvalidOperationException(resman.GetString("Exception_NoNestedTransactions"));
363 return new NpgsqlTransaction(this, level);
367 /// Opens a database connection with the property settings specified by the
368 /// <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
372 CheckConnectionClosed();
374 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
376 // Check if there is any missing argument.
377 if (! connection_string.Contains(ConnectionStringKeys.Host))
378 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"), ConnectionStringKeys.Host);
379 if (! connection_string.Contains(ConnectionStringKeys.UserName))
380 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"), ConnectionStringKeys.UserName);
382 // Get a Connector. The connector returned is guaranteed to be connected and ready to go.
383 connector = NpgsqlConnectorPool.ConnectorPoolMgr.RequestConnector (this);
385 connector.Notice += NoticeDelegate;
386 connector.Notification += NotificationDelegate;
390 /// This method changes the current database by disconnecting from the actual
391 /// database and connecting to the specified.
393 /// <param name="dbName">The name of the database to use in place of the current database.</param>
394 public void ChangeDatabase(String dbName)
398 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeDatabase", dbName);
401 throw new ArgumentNullException("dbName");
403 if (dbName == String.Empty)
404 throw new ArgumentOutOfRangeException(String.Format(resman.GetString("Exception_InvalidDbName"), dbName), "dbName");
406 String oldDatabaseName = Database;
410 connection_string[ConnectionStringKeys.Database] = dbName;
416 /// Releases the connection to the database. If the connection is pooled, it will be
417 /// made available for re-use. If it is non-pooled, the actual connection will be shutdown.
423 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
425 if (connector != null)
428 connector.Notification -= NotificationDelegate;
429 connector.Notice -= NoticeDelegate;
431 NpgsqlConnectorPool.ConnectorPoolMgr.ReleaseConnector(this, connector);
438 /// Creates and returns a <see cref="System.Data.IDbCommand">IDbCommand</see>
439 /// object associated with the <see cref="System.Data.IDbConnection">IDbConnection</see>.
441 /// <returns>A <see cref="System.Data.IDbCommand">IDbCommand</see> object.</returns>
442 IDbCommand IDbConnection.CreateCommand()
444 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.CreateCommand");
445 return (NpgsqlCommand) CreateCommand();
449 /// Creates and returns a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>
450 /// object associated with the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
452 /// <returns>A <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> object.</returns>
453 public NpgsqlCommand CreateCommand()
457 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateCommand");
458 return new NpgsqlCommand("", this);
462 /// Releases all resources used by the
463 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
465 /// <param name="disposing"><b>true</b> when called from Dispose();
466 /// <b>false</b> when being called from the finalizer.</param>
467 protected override void Dispose(bool disposing)
474 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
479 if (State != ConnectionState.Closed)
481 NpgsqlEventLog.LogMsg(resman, "Log_ConnectionLeaking", LogLevel.Debug);
482 NpgsqlConnectorPool.ConnectorPoolMgr.FixPoolCountBecauseOfConnectionDisposeFalse(this);
486 base.Dispose (disposing);
493 /// Create a new connection based on this one.
495 /// <returns>A new NpgsqlConnection object.</returns>
496 Object ICloneable.Clone()
502 /// Create a new connection based on this one.
504 /// <returns>A new NpgsqlConnection object.</returns>
505 public NpgsqlConnection Clone()
509 NpgsqlConnection C = new NpgsqlConnection(ConnectionString);
511 C.Notice += this.Notice;
513 if (connector != null)
522 // Internal methods and properties
524 internal void OnNotice(object O, NpgsqlNoticeEventArgs E)
532 internal void OnNotification(object O, NpgsqlNotificationEventArgs E)
534 if (Notification != null)
536 Notification(this, E);
541 /// The connector object connected to the backend.
543 internal NpgsqlConnector Connector
552 /// Gets the NpgsqlConnectionString containing the parsed connection string values.
554 internal NpgsqlConnectionString ConnectionStringValues {
557 return connection_string;
564 internal String UserName {
567 return connection_string.ToString(ConnectionStringKeys.UserName);
574 internal String Password {
577 return connection_string.ToString(ConnectionStringKeys.Password);
582 /// Determine if connection pooling will be used for this connection.
584 internal Boolean Pooling {
588 connection_string.ToBool(ConnectionStringKeys.Pooling, ConnectionStringDefaults.Pooling) &&
589 connection_string.ToInt32(ConnectionStringKeys.MaxPoolSize, ConnectionStringDefaults.MaxPoolSize) > 0
594 internal Int32 MinPoolSize {
597 return connection_string.ToInt32(ConnectionStringKeys.MinPoolSize, 0, MaxPoolSize, ConnectionStringDefaults.MinPoolSize);
601 internal Int32 MaxPoolSize {
604 return connection_string.ToInt32(ConnectionStringKeys.MaxPoolSize, 0, 1024, ConnectionStringDefaults.MaxPoolSize);
608 internal Int32 Timeout {
611 return connection_string.ToInt32(ConnectionStringKeys.Timeout, 0, 1024, ConnectionStringDefaults.Timeout);
622 /// Default SSL CertificateSelectionCallback implementation.
624 internal X509Certificate DefaultCertificateSelectionCallback(
625 X509CertificateCollection clientCertificates,
626 X509Certificate serverCertificate,
628 X509CertificateCollection serverRequestedCertificates)
630 if (CertificateSelectionCallback != null)
632 return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
641 /// Default SSL CertificateValidationCallback implementation.
643 internal bool DefaultCertificateValidationCallback(
644 X509Certificate certificate,
645 int[] certificateErrors)
647 if (CertificateValidationCallback != null)
649 return CertificateValidationCallback(certificate, certificateErrors);
658 /// Default SSL PrivateKeySelectionCallback implementation.
660 internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(
661 X509Certificate certificate,
664 if (PrivateKeySelectionCallback != null)
666 return PrivateKeySelectionCallback(certificate, targetHost);
677 // Private methods and properties
682 /// Write each key/value pair in the connection string to the log.
684 private void LogConnectionString()
686 foreach (DictionaryEntry DE in connection_string)
688 NpgsqlEventLog.LogMsg(resman, "Log_ConnectionStringValues", LogLevel.Debug, DE.Key, DE.Value);
692 private void CheckConnectionOpen()
696 throw new ObjectDisposedException(CLASSNAME);
699 if (connector == null)
701 throw new InvalidOperationException(resman.GetString("Exception_ConnNotOpen"));
705 private void CheckConnectionClosed()
709 throw new ObjectDisposedException(CLASSNAME);
712 if (connector != null)
714 throw new InvalidOperationException(resman.GetString("Exception_ConnOpen"));
718 private void CheckNotDisposed()
722 throw new ObjectDisposedException(CLASSNAME);
728 /// This method returns the collections we support.
730 public DataTable GetSchema()
734 DataTable result = new DataTable("Schema");
736 result.Columns.Add ("CollectionName", typeof (string));
737 result.Columns.Add ("NumberOfRestrictions", typeof (int));
740 DataRow row = result.NewRow();
741 row["CollectionName"] = "Databases";
742 row["NumberOfRestrictions"] = 0;
744 result.Rows.Add(row);
750 /// This method returns the collection data.
753 public DataTable GetSchema(String CollectionName)
756 if (CollectionName == "Databases")
757 return GetSchemaDatabases();
759 throw new NotSupportedException();
764 private DataTable GetSchemaDatabases()
766 NpgsqlConnection conn = new NpgsqlConnection(this.ConnectionString);
768 NpgsqlCommand c = new NpgsqlCommand("SELECT d.datname as \"Name\", u.usename as \"Owner\", pg_catalog.pg_encoding_to_char(d.encoding) as \"Encoding\" FROM pg_catalog.pg_database d LEFT JOIN pg_catalog.pg_user u ON d.datdba = u.usesysid ORDER BY 1;", conn);
771 DataTable result = new DataTable("Databases");
773 result.Columns.Add ("Name", typeof (string));
774 result.Columns.Add ("Owner", typeof (string));
775 result.Columns.Add ("Encoding", typeof (string));
778 NpgsqlDataReader dr = c.ExecuteReader();
783 DataRow row = result.NewRow();
784 row["Name"] = dr["Name"];
785 row["Owner"] = dr["Owner"];
786 row["Encoding"] = dr["Encoding"];
788 result.Rows.Add(row);