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.Net.Sockets;
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.Security.Cryptography.X509Certificates;
36 using Mono.Security.Protocol.Tls;
44 /// Represents the method that handles the <see cref="Npgsql.NpgsqlConnection.Notification">Notification</see> events.
46 /// <param name="sender">The source of the event.</param>
47 /// <param name="e">A <see cref="Npgsql.NpgsqlNotificationEventArgs">NpgsqlNotificationEventArgs</see> that contains the event data.</param>
48 public delegate void NotificationEventHandler(Object sender, NpgsqlNotificationEventArgs e);
51 /// This class represents a connection to a
52 /// PostgreSQL server.
54 [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlConnection))]
55 public sealed class NpgsqlConnection : Component, IDbConnection, ICloneable
57 //Changed the Name of this event because events usually don't start with 'On' in the .Net-Framework
58 // (but their handlers do ;-)
60 /// Occurs on NotificationResponses from the PostgreSQL backend.
62 public event NotificationEventHandler Notification;
65 // Public properties for ssl callbacks
66 public CertificateValidationCallback CertificateValidationCallback;
67 public CertificateSelectionCallback CertificateSelectionCallback;
68 public PrivateKeySelectionCallback PrivateKeySelectionCallback;
72 private NpgsqlState state;
74 private ConnectionState connection_state;
75 private String connection_string;
76 internal ListDictionary connection_string_values;
77 // some of the following constants are needed
78 // for designtime support so I made them 'internal'
79 // as I didn't want to add another interface for internal access
81 // In the connection string
82 internal readonly Char CONN_DELIM = ';'; // Delimeter
83 internal readonly Char CONN_ASSIGN = '=';
84 internal readonly String CONN_SERVER = "SERVER";
85 internal readonly String CONN_USERID = "USER ID";
86 internal readonly String CONN_PASSWORD = "PASSWORD";
87 internal readonly String CONN_DATABASE = "DATABASE";
88 internal readonly String CONN_PORT = "PORT";
89 internal readonly String SSL_ENABLED = "SSL";
90 // Postgres default port
91 internal readonly String PG_PORT = "5432";
93 // These are for ODBC connection string compatibility
94 internal readonly String ODBC_USERID = "UID";
95 internal readonly String ODBC_PASSWORD = "PWD";
97 // These are for the connection pool
98 internal readonly String MIN_POOL_SIZE = "MINPOOLSIZE";
99 internal readonly String MAX_POOL_SIZE = "MAXPOOLSIZE";
101 internal readonly String CONN_ENCODING = "ENCODING";
103 internal readonly String CONN_TIMEOUT = "TIMEOUT";
106 // Values for possible CancelRequest messages.
107 private NpgsqlBackEndKeyData backend_keydata;
109 // Flag for transaction status.
110 private Boolean _inTransaction = false;
112 // Mediator which will hold data generated from backend
113 private NpgsqlMediator _mediator;
115 // Logging related values
116 private readonly String CLASSNAME = "NpgsqlConnection";
118 private Stream stream;
120 private Connector _connector;
122 private Encoding connection_encoding;
124 private Boolean _supportsPrepare = false;
126 private String _serverVersion; // Contains string returned from select version();
128 private Hashtable _oidToNameMapping;
130 private System.Resources.ResourceManager resman;
132 private Int32 _backendProtocolVersion;
134 private Int32 _connectionTimeout;
138 /// Initializes a new instance of the
139 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class.
141 public NpgsqlConnection() : this(String.Empty)
145 /// Initializes a new instance of the
146 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> class
147 /// and sets the <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
149 /// <param name="ConnectionString">The connection used to open the PostgreSQL database.</param>
150 public NpgsqlConnection(String ConnectionString)
152 resman = new System.Resources.ResourceManager(this.GetType());
153 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME, ConnectionString);
155 connection_state = ConnectionState.Closed;
156 state = NpgsqlClosedState.Instance;
157 connection_string = ConnectionString;
158 connection_string_values = new ListDictionary();
159 connection_encoding = Encoding.Default;
160 _backendProtocolVersion = ProtocolVersion.Version3;
162 _mediator = new NpgsqlMediator();
163 _oidToNameMapping = new Hashtable();
165 _connectionTimeout = 15;
167 CertificateValidationCallback = new CertificateValidationCallback(DefaultCertificateValidationCallback);
170 if (connection_string != String.Empty)
171 ParseConnectionString();
176 /// Gets or sets the string used to open a SQL Server database.
178 /// <value>The connection string that includes the server name,
179 /// the database name, and other parameters needed to establish
180 /// the initial connection. The default value is an empty string.
182 [RefreshProperties(RefreshProperties.All), DefaultValue(""), RecommendedAsConfigurable(true)]
183 [NpgsqlSysDescription("Description_ConnectionString", typeof(NpgsqlConnection)), Category("Data")]
184 [Editor(typeof(ConnectionStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
185 public String ConnectionString {
188 return connection_string;
192 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "ConnectionString", value);
193 connection_string = value;
194 if (connection_string != String.Empty)
195 ParseConnectionString();
200 /// Gets the time to wait while trying to establish a connection
201 /// before terminating the attempt and generating an error.
203 /// <value>The time (in seconds) to wait for a connection to open. The default value is 15 seconds.</value>
204 [NpgsqlSysDescription("Description_ConnectionTimeout", typeof(NpgsqlConnection))]
205 public Int32 ConnectionTimeout {
208 return _connectionTimeout;
213 /// Gets the name of the current database or the database to be used after a connection is opened.
215 /// <value>The name of the current database or the name of the database to be
216 /// used after a connection is opened. The default value is an empty string.</value>
217 [NpgsqlSysDescription("Description_Database", typeof(NpgsqlConnection))]
218 public String Database {
226 /// Gets the current state of the connection.
228 /// <value>A bitwise combination of the <see cref="System.Data.ConnectionState">ConnectionState</see> values. The default is <b>Closed</b>.</value>
230 public ConnectionState State {
233 return connection_state;
238 /// Begins a database transaction.
240 /// <returns>An <see cref="System.Data.IDbTransaction">IDbTransaction</see>
241 /// object representing the new transaction.</returns>
243 /// Currently there's no support for nested transactions.
245 IDbTransaction IDbConnection.BeginTransaction()
247 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.BeginTransaction");
248 //throw new NotImplementedException();
249 return BeginTransaction();
253 /// Begins a database transaction with the specified isolation level.
255 /// <param name="level">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
256 /// <returns>An <see cref="System.Data.IDbTransaction">IDbTransaction</see>
257 /// object representing the new transaction.</returns>
259 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
260 /// There's no support for nested transactions.
262 IDbTransaction IDbConnection.BeginTransaction(IsolationLevel level)
264 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.BeginTransaction", level);
265 //throw new NotImplementedException();
266 return BeginTransaction(level);
270 // I had to rename this Method from Notification to Notify due to the renaming of OnNotification to Notification
272 /// Creates a Notification event
274 /// <param name="e">The <see cref="Npgsql.NpgsqlNotificationEventArgs">NpgsqlNotificationEventArgs</see> that contains the event data.</param>
275 internal void Notify(NpgsqlNotificationEventArgs e)
277 if (Notification != null)
278 Notification(this, e);
282 /// Begins a database transaction.
284 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
285 /// object representing the new transaction.</returns>
287 /// Currently there's no support for nested transactions.
289 public NpgsqlTransaction BeginTransaction()
291 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction");
292 return this.BeginTransaction(IsolationLevel.ReadCommitted);
296 /// Begins a database transaction with the specified isolation level.
298 /// <param name="level">The <see cref="System.Data.IsolationLevel">isolation level</see> under which the transaction should run.</param>
299 /// <returns>A <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
300 /// object representing the new transaction.</returns>
302 /// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
303 /// There's no support for nested transactions.
305 public NpgsqlTransaction BeginTransaction(IsolationLevel level)
307 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "BeginTransaction", level);
310 throw new InvalidOperationException(resman.GetString("Exception_NoNestedTransactions"));
313 return new NpgsqlTransaction(this, level);
317 /// This method changes the current database by disconnecting from the actual
318 /// database and connecting to the specified.
320 /// <param name="dbName">The name of the database to use in place of the current database.</param>
321 public void ChangeDatabase(String dbName)
323 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeDatabase", dbName);
324 //throw new NotImplementedException();
327 throw new ArgumentNullException("dbName");
329 if (dbName == String.Empty)
330 throw new ArgumentException(String.Format(resman.GetString("Exception_InvalidDbName"), dbName), "dbName");
332 if(this.connection_state != ConnectionState.Open)
333 throw new InvalidOperationException(resman.GetString("Exception_ChangeDatabaseOnOpenConn"));
335 String oldDatabaseName = (String)connection_string_values[CONN_DATABASE];
338 connection_string_values[CONN_DATABASE] = dbName;
347 /// Opens a database connection with the property settings specified by the
348 /// <see cref="Npgsql.NpgsqlConnection.ConnectionString">ConnectionString</see>.
352 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
354 // I moved this here from ParseConnectionString as there is no need to validate the
355 // ConnectionString before we open the connection.
356 // See: http://gborg.postgresql.org/pipermail/npgsql-hackers/2003-March/000019.html
357 // In fact it makes it possible to parse incomplete ConnectionStrings for designtime support
360 // Now check if there is any missing argument.
361 if (connection_string == String.Empty)
362 throw new InvalidOperationException(resman.GetString("Exception_ConnStrEmpty"));
363 if (connection_string_values[CONN_SERVER] == null)
364 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"), CONN_SERVER);
365 if ((connection_string_values[CONN_USERID] == null) & (connection_string_values[ODBC_USERID] == null))
366 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"), CONN_USERID);
367 if ((connection_string_values[CONN_PASSWORD] == null) & (connection_string_values[ODBC_PASSWORD] == null))
368 throw new ArgumentException(resman.GetString("Exception_MissingConnStrArg"), CONN_PASSWORD);
369 if (connection_string_values[CONN_DATABASE] == null)
370 // Database is optional. "[...] defaults to the user name if empty"
371 connection_string_values[CONN_DATABASE] = connection_string_values[CONN_USERID];
372 if (connection_string_values[CONN_PORT] == null)
373 // Port is optional. Defaults to PG_PORT.
374 connection_string_values[CONN_PORT] = PG_PORT;
375 if (connection_string_values[SSL_ENABLED] == null)
376 connection_string_values[SSL_ENABLED] = "no";
377 if (connection_string_values[MIN_POOL_SIZE] == null)
378 connection_string_values[MIN_POOL_SIZE] = "1";
379 if (connection_string_values[MAX_POOL_SIZE] == null)
380 connection_string_values[MAX_POOL_SIZE] = "20";
381 if (connection_string_values[CONN_ENCODING] == null)
382 connection_string_values[CONN_ENCODING] = "SQL_ASCII";
383 if (connection_string_values[CONN_TIMEOUT] == null)
384 connection_string_values[CONN_TIMEOUT] = "15";
389 // Check if the connection is already open.
390 if (connection_state == ConnectionState.Open)
391 throw new NpgsqlException(resman.GetString("Exception_ConnOpen"));
393 lock(ConnectorPool.ConnectorPoolMgr)
395 Connector = ConnectorPool.ConnectorPoolMgr.RequestConnector(ConnectionString,
396 Int32.Parse((String)connection_string_values[MAX_POOL_SIZE]),
397 Int32.Parse((String)connection_string_values[CONN_TIMEOUT]),
399 Connector.InUse = true;
402 if (!Connector.IsInitialized)
405 // Reset state to initialize new connector in pool.
406 CurrentState = NpgsqlClosedState.Instance;
408 // Try first connect using the 3.0 protocol...
409 CurrentState.Open(this);
411 // Change the state of connection to open.
412 connection_state = ConnectionState.Open;
414 // Check if there were any errors.
415 if (_mediator.Errors.Count > 0)
417 // Check if there is an error of protocol not supported...
418 // As the message can be localized, just check the initial unlocalized part of the
419 // message. If it is an error other than protocol error, when connecting using
420 // version 2.0 we shall catch the error again.
421 if (((String)_mediator.Errors[0]).StartsWith("FATAL"))
423 // Try using the 2.0 protocol.
425 CurrentState = NpgsqlClosedState.Instance;
426 BackendProtocolVersion = ProtocolVersion.Version2;
427 CurrentState.Open(this);
430 // Keep checking for errors...
431 if(_mediator.Errors.Count > 0)
433 StringWriter sw = new StringWriter();
434 sw.WriteLine(resman.GetString("Exception_OpenError"));
436 foreach(string error in _mediator.Errors)
438 sw.WriteLine("{0}. {1}", i++, error);
440 CurrentState = NpgsqlClosedState.Instance;
442 throw new NpgsqlException(sw.ToString());
446 backend_keydata = _mediator.GetBackEndKeyData();
448 // Get version information to enable/disable server version features.
449 // Only for protocol 2.0.
450 if (BackendProtocolVersion == ProtocolVersion.Version2)
452 NpgsqlCommand command = new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
453 _serverVersion = (String) command.ExecuteScalar();
456 // Adjust client encoding.
458 //NpgsqlCommand commandEncoding = new NpgsqlCommand("show client_encoding", this);
459 //String clientEncoding = (String)commandEncoding.ExecuteScalar();
461 if (connection_string_values[CONN_ENCODING].Equals("UNICODE"))
462 connection_encoding = Encoding.UTF8;
465 Connector.ServerVersion = ServerVersion;
466 Connector.BackendProtocolVersion = BackendProtocolVersion;
467 Connector.Encoding = connection_encoding;
471 // Connector was obtained from pool.
472 // Do a mini initialization in the state machine.
474 connection_state = ConnectionState.Open;
475 ServerVersion = Connector.ServerVersion;
476 BackendProtocolVersion = Connector.BackendProtocolVersion;
477 Encoding = Connector.Encoding;
479 CurrentState = NpgsqlReadyState.Instance;
481 ProcessServerVersion();
482 _oidToNameMapping = NpgsqlTypesHelper.LoadTypesMapping(this);
491 // This exception was thrown by StartupPacket handling functions.
492 // So, close the connection and throw the exception.
493 // [TODO] Better exception handling. :)
496 throw new NpgsqlException(resman.GetString("Exception_OpenError"), e);
502 /// Closes the connection to the database.
511 /// Creates and returns a <see cref="System.Data.IDbCommand">IDbCommand</see>
512 /// object associated with the <see cref="System.Data.IDbConnection">IDbConnection</see>.
514 /// <returns>A <see cref="System.Data.IDbCommand">IDbCommand</see> object.</returns>
515 IDbCommand IDbConnection.CreateCommand()
517 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbConnection.CreateCommand");
518 return (NpgsqlCommand) CreateCommand();
522 /// Creates and returns a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>
523 /// object associated with the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
525 /// <returns>A <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> object.</returns>
526 public NpgsqlCommand CreateCommand()
528 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateCommand");
529 return new NpgsqlCommand("", this);
533 /// Releases the unmanaged resources used by the
534 /// <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
535 /// and optionally releases the managed resources.
537 /// <param name="disposing"><b>true</b> to release both managed and unmanaged resources;
538 /// <b>false</b> to release only unmanaged resources.</param>
539 protected override void Dispose(bool disposing)
543 // Only if explicitly calling Close or dispose we still have access to
544 // managed resources.
545 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose", disposing);
549 if ((connection_state == ConnectionState.Open))
551 CurrentState.Close(this);
555 catch (IOException e)
557 throw new NpgsqlException(resman.GetString("Exception_CloseError"), e);
561 // Even if an exception occurs, let object in a consistent state.
562 /*if (stream != null)
564 connection_state = ConnectionState.Closed;
568 base.Dispose (disposing);
572 public Object Clone()
574 return new NpgsqlConnection(ConnectionString);
577 // Private util methods
580 /// This method parses the connection string.
581 /// It translates it to a list of key-value pairs.
582 /// Valid values are:
583 /// Server - Address/Name of Postgresql Server
584 /// Port - Port to connect to.
585 /// Database - Database name. Defaults to user name if not specified
587 /// Password - Password for clear text authentication
588 /// MinPoolSize - Min size of connection pool
589 /// MaxPoolSize - Max size of connection pool
590 /// Encoding - Encoding to be used
591 /// Timeout - Time to wait for connection open. In seconds.
593 private void ParseConnectionString()
595 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ParseConnectionString");
597 connection_string_values.Clear();
599 // Get the key-value pairs delimited by CONN_DELIM
600 String[] pairs = connection_string.Split(new Char[] {CONN_DELIM});
603 // Now, for each pair, get its key-value.
604 foreach(String s in pairs)
606 // This happen when there are trailling/empty CONN_DELIMs
611 keyvalue = s.Split(new Char[] {CONN_ASSIGN});
613 // Check if there is a key-value pair.
614 if (keyvalue.Length != 2)
615 throw new ArgumentException(resman.GetString("Exception_WrongKeyVal"), connection_string);
617 // Shift the key to upper case, and substitute ODBC style keys
618 keyvalue[0] = keyvalue[0].ToUpper();
619 if (keyvalue[0] == ODBC_USERID)
620 keyvalue[0] = CONN_USERID;
621 if (keyvalue[0] == ODBC_PASSWORD)
622 keyvalue[0] = CONN_PASSWORD;
624 // Add the pair to the dictionary. The key is shifted to upper
625 // case for case insensitivity.
627 NpgsqlEventLog.LogMsg(resman, "Log_ConnectionStringValues", LogLevel.Debug, keyvalue[0], keyvalue[1]);
628 connection_string_values.Add(keyvalue[0], keyvalue[1]);
634 /// This method is required to set all the version dependent features flags.
635 /// SupportsPrepare means the server can use prepared query plans (7.3+)
637 private void ProcessServerVersion ()
639 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessServerVersion");
640 // FIXME: Do a better parsing of server version to avoid
641 // hardcode version numbers.
643 if (BackendProtocolVersion == ProtocolVersion.Version2)
645 // With protocol version 2, only 7.3 version supports prepare.
646 SupportsPrepare = (_serverVersion.IndexOf("PostgreSQL 7.3") != -1);
650 // 3.0+ version is set by ParameterStatus message.
651 // On protocol 3.0, 7.4 and above support it.
652 SupportsPrepare = (_serverVersion.IndexOf("7.4") != -1)
653 || (_serverVersion.IndexOf("7.5") != -1);
657 internal Stream Stream {
660 return _connector.Stream;
668 internal Connector Connector
683 if (SSL_ENABLED=="yes")
691 internal void Query (NpgsqlCommand queryCommand)
693 CurrentState.Query(this, queryCommand );
696 internal void Authenticate (string password)
698 CurrentState.Authenticate(this, password );
701 internal void Startup ()
703 CurrentState.Startup(this);
706 internal void Parse (NpgsqlParse parse)
708 CurrentState.Parse(this, parse);
711 internal void Flush ()
713 CurrentState.Flush(this);
716 internal void Sync ()
718 CurrentState.Sync(this);
721 internal void Bind (NpgsqlBind bind)
723 CurrentState.Bind(this, bind);
726 internal void Execute (NpgsqlExecute execute)
728 CurrentState.Execute(this, execute);
732 // Default SSL Callbacks implementation.
733 private Boolean DefaultCertificateValidationCallback(
734 X509Certificate certificate,
735 int[] certificateErrors)
742 internal NpgsqlState CurrentState {
753 internal NpgsqlBackEndKeyData BackEndKeyData {
756 return backend_keydata;
760 backend_keydata = value;
764 internal String ServerName {
767 return (String)connection_string_values[CONN_SERVER];
771 internal String ServerPort {
774 return (String)connection_string_values[CONN_PORT];
778 internal String DatabaseName {
781 return (String)connection_string_values[CONN_DATABASE];
785 internal String UserName {
788 return (String)connection_string_values[CONN_USERID];
792 internal String ServerPassword {
795 return (String)connection_string_values[CONN_PASSWORD];
799 internal String SSL {
802 return (String)connection_string_values[SSL_ENABLED];
806 internal Encoding Encoding {
809 return connection_encoding;
814 connection_encoding = value;
818 internal NpgsqlMediator Mediator {
825 internal Boolean InTransaction {
828 return _inTransaction;
832 _inTransaction = value;
836 internal Boolean SupportsPrepare {
839 return _supportsPrepare;
843 _supportsPrepare = value;
847 internal String ServerVersion {
850 return _serverVersion;
854 _serverVersion = value;
858 internal Hashtable OidToNameMapping {
861 return _oidToNameMapping;
865 _oidToNameMapping = value;
870 internal Int32 BackendProtocolVersion {
873 return _backendProtocolVersion;
877 _backendProtocolVersion = value;
881 internal Int32 MinPoolSize {
884 return Int32.Parse((String)connection_string_values[MIN_POOL_SIZE]);
888 internal Int32 MaxPoolSize {
891 return Int32.Parse((String)connection_string_values[MAX_POOL_SIZE]);