2 // System.Data.SqlClient.SqlConnection.cs
5 // Rodrigo Moya (rodrigo@ximian.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
7 // Tim Coleman (tim@timcoleman.com)
8 // Phillip Jerkins (Phillip.Jerkins@morgankeegan.com)
9 // Diego Caravana (diego@toth.it)
11 // Copyright (C) Ximian, Inc 2002
12 // Copyright (C) Daniel Morgan 2002, 2003
13 // Copyright (C) Tim Coleman, 2002, 2003
14 // Copyright (C) Phillip Jerkins, 2003
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using Mono.Data.Tds.Protocol;
43 using System.Collections;
44 using System.Collections.Specialized;
45 using System.ComponentModel;
47 using System.Data.Common;
50 using System.Data.ProviderBase;
53 using System.EnterpriseServices;
54 using System.Globalization;
56 using System.Net.Sockets;
60 namespace System.Data.SqlClient {
61 [DefaultEvent ("InfoMessage")]
63 public sealed class SqlConnection : DbConnectionBase, IDbConnection, ICloneable
65 public sealed class SqlConnection : Component, IDbConnection, ICloneable
69 bool disposed = false;
71 // The set of SQL connection pools
72 static TdsConnectionPoolManager sqlConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds70);
74 // The current connection pool
75 TdsConnectionPool pool;
77 // The connection string that identifies this connection
78 string connectionString = null;
80 // The transaction object for the current transaction
81 SqlTransaction transaction = null;
83 // Connection parameters
84 TdsConnectionParameters parms = new TdsConnectionParameters ();
85 NameValueCollection connStringParameters = null;
89 int connectionTimeout;
96 ConnectionState state = ConnectionState.Closed;
98 SqlDataReader dataReader = null;
99 XmlReader xmlReader = null;
108 public SqlConnection ()
109 : this (String.Empty)
113 public SqlConnection (string connectionString)
115 Init (connectionString);
119 internal SqlConnection (DbConnectionFactory connectionFactory) : base (connectionFactory)
126 private void Init (string connectionString)
128 connectionTimeout = 15; // default timeout
129 dataSource = ""; // default datasource
130 packetSize = 8192; // default packetsize
131 ConnectionString = connectionString;
136 #endregion // Constructors
140 [DataCategory ("Data")]
141 [DataSysDescription ("Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'.")]
143 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
144 [RecommendedAsConfigurable (true)]
145 [RefreshProperties (RefreshProperties.All)]
146 [MonoTODO("persist security info, encrypt, enlist and , attachdbfilename keyword not implemented")]
151 string ConnectionString {
152 get { return connectionString; }
153 set { SetConnectionString (value); }
156 [DataSysDescription ("Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.")]
157 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
162 int ConnectionTimeout {
163 get { return connectionTimeout; }
166 [DataSysDescription ("Current SQL Server database, 'Initial Catalog=X' in the ConnectionString.")]
167 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
174 if (State == ConnectionState.Open)
176 return GetConnStringKeyValue ("DATABASE", "INITIAL CATALOG");
180 internal SqlDataReader DataReader {
181 get { return dataReader; }
182 set { dataReader = value; }
185 [DataSysDescription ("Current SqlServer that the connection is opened to, 'Data Source=X' in the ConnectionString.")]
186 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
192 get { return dataSource; }
195 [DataSysDescription ("Network packet size, 'Packet Size=x' in the ConnectionString.")]
196 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
197 public int PacketSize {
198 get { return packetSize; }
202 [DataSysDescription ("Version of the SQL Server accessed by the SqlConnection.")]
203 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
208 string ServerVersion {
209 get { return tds.ServerVersion; }
213 [DataSysDescription ("The ConnectionState indicating whether the connection is open or closed.")]
214 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
219 ConnectionState State {
220 get { return state; }
227 internal SqlTransaction Transaction {
228 get { return transaction; }
229 set { transaction = value; }
232 [DataSysDescription ("Workstation Id, 'Workstation Id=x' in the ConnectionString.")]
233 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
234 public string WorkstationId {
235 get { return parms.Hostname; }
238 internal XmlReader XmlReader {
239 get { return xmlReader; }
240 set { xmlReader = value; }
243 #endregion // Properties
247 [DataCategory ("InfoMessage")]
248 [DataSysDescription ("Event triggered when messages arrive from the DataSource.")]
249 public event SqlInfoMessageEventHandler InfoMessage;
251 [DataCategory ("StateChange")]
252 [DataSysDescription ("Event triggered when the connection changes state.")]
257 event StateChangeEventHandler StateChange;
263 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
265 throw new SqlException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SqlClient Data Provider", e.State);
268 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
270 OnSqlInfoMessage (CreateSqlInfoMessageEvent (e.Errors));
273 #endregion // Delegates
277 internal string GetConnStringKeyValue (params string [] keys)
279 if (connStringParameters == null || connStringParameters.Count == 0)
281 foreach (string key in keys) {
282 string value = connStringParameters [key];
291 public new SqlTransaction BeginTransaction ()
293 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
296 public new SqlTransaction BeginTransaction (IsolationLevel iso)
298 return BeginTransaction (iso, String.Empty);
301 public SqlTransaction BeginTransaction (string transactionName)
303 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
306 public SqlTransaction BeginTransaction (IsolationLevel iso, string transactionName)
308 if (state == ConnectionState.Closed)
309 throw new InvalidOperationException ("The connection is not open.");
310 if (transaction != null)
311 throw new InvalidOperationException ("SqlConnection does not support parallel transactions.");
313 if (iso == IsolationLevel.Chaos)
314 throw new ArgumentException ("Invalid IsolationLevel parameter: must be ReadCommitted, ReadUncommitted, RepeatableRead, or Serializable.");
316 string isolevel = String.Empty;
318 case IsolationLevel.ReadCommitted:
319 isolevel = "READ COMMITTED";
321 case IsolationLevel.ReadUncommitted:
322 isolevel = "READ UNCOMMITTED";
324 case IsolationLevel.RepeatableRead:
325 isolevel = "REPEATABLE READ";
327 case IsolationLevel.Serializable:
328 isolevel = "SERIALIZABLE";
332 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0};BEGIN TRANSACTION {1}", isolevel, transactionName));
334 transaction = new SqlTransaction (this, iso);
342 void ChangeDatabase (string database)
344 if (!IsValidDatabaseName (database))
345 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
346 if (state != ConnectionState.Open)
347 throw new InvalidOperationException ("The connection is not open.");
348 tds.Execute (String.Format ("use {0}", database));
351 private void ChangeState (ConnectionState currentState)
353 ConnectionState originalState = state;
354 state = currentState;
355 OnStateChange (CreateStateChangeEvent (originalState, currentState));
364 if (transaction != null && transaction.IsOpen)
365 transaction.Rollback ();
367 if (dataReader != null || xmlReader != null) {
368 if(tds != null) tds.SkipToEnd ();
374 if(pool != null) pool.ReleaseConnection (tds);
376 if(tds != null) tds.Disconnect ();
379 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
380 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
383 ChangeState (ConnectionState.Closed);
386 public new SqlCommand CreateCommand ()
388 SqlCommand command = new SqlCommand ();
389 command.Connection = this;
393 private SqlInfoMessageEventArgs CreateSqlInfoMessageEvent (TdsInternalErrorCollection errors)
395 return new SqlInfoMessageEventArgs (errors);
398 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
400 return new StateChangeEventArgs (originalState, currentState);
403 protected override void Dispose (bool disposing)
408 if (State == ConnectionState.Open)
411 dataSource = ""; // default dataSource
412 ConnectionString = null;
416 base.Dispose (disposing);
422 [MonoTODO ("Not sure what this means at present.")]
427 void EnlistDistributedTransaction (ITransaction transaction)
429 throw new NotImplementedException ();
432 object ICloneable.Clone ()
434 return new SqlConnection (ConnectionString);
437 IDbTransaction IDbConnection.BeginTransaction ()
439 return BeginTransaction ();
442 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
444 return BeginTransaction (iso);
447 IDbCommand IDbConnection.CreateCommand ()
449 return CreateCommand ();
452 void IDisposable.Dispose ()
455 GC.SuppressFinalize (this);
464 string serverName = "";
465 if (connectionString == null)
466 throw new InvalidOperationException ("Connection string has not been initialized.");
470 if(!ParseDataSource (dataSource, out port, out serverName))
471 throw new SqlException(20, 0, "SQL Server does not exist or access denied.", 17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
472 tds = new Tds70 (serverName, port, PacketSize, ConnectionTimeout);
475 if(!ParseDataSource (dataSource, out port, out serverName))
476 throw new SqlException(20, 0, "SQL Server does not exist or access denied.", 17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
478 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
479 pool = sqlConnectionPools.GetConnectionPool (connectionString, info);
480 tds = pool.GetConnection ();
483 catch (TdsTimeoutException e) {
484 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
487 tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
488 tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
490 if (!tds.IsConnected) {
496 pool.ReleaseConnection (tds);
501 /* Not sure ebout removing these 2 lines.
502 * The command that gets to the sql server is just
503 * 'sp_reset_connection' and it fails.
504 * Either remove them definitely or fix it
505 else if (connectionReset)
506 tds.ExecProc ("sp_reset_connection");
508 disposed = false; // reset this, so using () would call Close ().
509 ChangeState (ConnectionState.Open);
512 private bool ParseDataSource (string theDataSource, out int thePort, out string theServerName)
515 string theInstanceName = "";
516 if ((theDataSource == null) || (theServerName == null)
517 || theDataSource == "")
518 throw new ArgumentException("Format of initialization string doesnot conform to specifications");
520 thePort = 1433; // default TCP port for SQL Server
524 if ((idx = theDataSource.IndexOf (",")) > -1) {
525 theServerName = theDataSource.Substring (0, idx);
526 string p = theDataSource.Substring (idx + 1);
527 thePort = Int32.Parse (p);
529 else if ((idx = theDataSource.IndexOf ("\\")) > -1) {
530 theServerName = theDataSource.Substring (0, idx);
531 theInstanceName = theDataSource.Substring (idx + 1);
532 // do port discovery via UDP port 1434
533 port = DiscoverTcpPortViaSqlMonitor (theServerName, theInstanceName);
538 theServerName = theDataSource;
541 if(theServerName.Equals("(local)"))
542 theServerName = "localhost";
547 private bool ConvertIntegratedSecurity (string value)
549 if (value.ToUpper() == "SSPI")
554 return ConvertToBoolean("integrated security", value);
557 private bool ConvertToBoolean(string key, string value)
559 string upperValue = value.ToUpper();
561 if (upperValue == "TRUE" ||upperValue == "YES")
565 else if (upperValue == "FALSE" || upperValue == "NO")
570 throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
571 "Invalid value \"{0}\" for key '{1}'.", value, key));
574 private int ConvertToInt32(string key, string value)
578 return int.Parse(value);
582 throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
583 "Invalid value \"{0}\" for key '{1}'.", value, key));
587 private int DiscoverTcpPortViaSqlMonitor(string ServerName, string InstanceName)
589 SqlMonitorSocket msock;
590 msock = new SqlMonitorSocket (ServerName, InstanceName);
591 int SqlServerPort = msock.DiscoverTcpPort ();
593 return SqlServerPort;
596 void SetConnectionString (string connectionString)
598 if (( connectionString == null)||( connectionString.Length == 0)) {
599 this.connectionString = null;
603 NameValueCollection parameters = new NameValueCollection ();
604 connectionString += ";";
606 SetDefaultConnectionParameters (parameters);
608 bool inQuote = false;
609 bool inDQuote = false;
612 string name = String.Empty;
613 string value = String.Empty;
614 StringBuilder sb = new StringBuilder ();
616 for (int i = 0; i < connectionString.Length; i += 1) {
617 char c = connectionString [i];
619 if (i == connectionString.Length - 1)
622 peek = connectionString [i + 1];
628 else if (peek.Equals (c)) {
638 else if (peek.Equals (c)) {
643 inDQuote = !inDQuote;
646 if (inDQuote || inQuote)
649 if (name != String.Empty && name != null) {
650 value = sb.ToString ();
651 parameters [name.ToUpper ().Trim ()] = value.Trim ();
655 value = String.Empty;
656 sb = new StringBuilder ();
660 if (inDQuote || inQuote || !inName)
662 else if (peek.Equals (c)) {
667 name = sb.ToString ();
668 sb = new StringBuilder ();
673 if (inQuote || inDQuote)
675 else if (sb.Length > 0 && !peek.Equals (';'))
684 SetProperties (parameters);
686 this.connectionString = connectionString;
687 this.connStringParameters = parameters;
690 void SetDefaultConnectionParameters (NameValueCollection parameters)
692 if (null == parameters.Get ("APPLICATION NAME") && null == parameters.Get ("APP"))
693 parameters["APPLICATION NAME"] = "Mono SqlClient Data Provider";
694 if (null == parameters.Get ("TIMEOUT") && null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
695 parameters["CONNECT TIMEOUT"] = "15";
696 if (null == parameters.Get ("CONNECTION LIFETIME"))
697 parameters["CONNECTION LIFETIME"] = "0";
698 if (null == parameters.Get ("CONNECTION RESET"))
699 parameters["CONNECTION RESET"] = "true";
700 if (null == parameters.Get ("ENLIST"))
701 parameters["ENLIST"] = "true";
702 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
703 parameters["INTEGRATED SECURITY"] = "false";
704 if (null == parameters.Get ("MAX POOL SIZE"))
705 parameters["MAX POOL SIZE"] = "100";
706 if (null == parameters.Get ("MIN POOL SIZE"))
707 parameters["MIN POOL SIZE"] = "0";
708 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET") && null == parameters.Get ("NETWORK"))
709 parameters["NETWORK LIBRARY"] = "dbmssocn";
710 if (null == parameters.Get ("PACKET SIZE"))
711 parameters["PACKET SIZE"] = "512";
712 if (null == parameters.Get ("PERSIST SECURITY INFO") && null == parameters.Get ("PERSISTSECURITYINFO"))
713 parameters["PERSIST SECURITY INFO"] = "false";
714 if (null == parameters.Get ("POOLING"))
715 parameters["POOLING"] = "true";
716 if (null == parameters.Get ("WORKSTATION ID") && null == parameters.Get ("WSID"))
717 parameters["WORKSTATION ID"] = Dns.GetHostName();
720 private void SetProperties (NameValueCollection parameters)
722 foreach (string name in parameters) {
723 string value = parameters[name];
727 case "APPLICATION NAME" :
728 parms.ApplicationName = value;
730 case "ATTACHDBFILENAME" :
731 case "EXTENDED PROPERTIES" :
732 case "INITIAL FILE NAME" :
733 throw new NotImplementedException("Attachable database support is not implemented.");
735 case "CONNECT TIMEOUT" :
736 case "CONNECTION TIMEOUT" :
737 connectionTimeout = ConvertToInt32 ("connection timeout", value);
739 case "CONNECTION LIFETIME" :
741 case "CONNECTION RESET" :
742 connectionReset = ConvertToBoolean ("connection reset", value);
745 case "CURRENT LANGUAGE" :
746 parms.Language = value;
752 case "NETWORK ADDRESS" :
756 if (ConvertToBoolean("encrypt", value))
758 throw new NotImplementedException("SSL encryption for"
759 + " data sent between client and server is not"
764 if (!ConvertToBoolean("enlist", value))
766 throw new NotImplementedException("Disabling the automatic"
767 + " enlistment of connections in the thread's current"
768 + " transaction context is not implemented.");
771 case "INITIAL CATALOG" :
773 parms.Database = value;
775 case "INTEGRATED SECURITY" :
776 case "TRUSTED_CONNECTION" :
777 parms.DomainLogin = ConvertIntegratedSecurity(value);
779 case "MAX POOL SIZE" :
780 maxPoolSize = ConvertToInt32 ("max pool size", value);
782 case "MIN POOL SIZE" :
783 minPoolSize = ConvertToInt32 ("min pool size", value);
786 case "MULTIPLEACTIVERESULTSETS":
791 case "NETWORK LIBRARY" :
792 if (!value.ToUpper ().Equals ("DBMSSOCN"))
793 throw new ArgumentException ("Unsupported network library.");
796 packetSize = ConvertToInt32 ("packet size", value);
800 parms.Password = value;
802 case "PERSISTSECURITYINFO" :
803 case "PERSIST SECURITY INFO" :
804 // FIXME : not implemented
807 pooling = ConvertToBoolean("pooling", value);
815 case "WORKSTATION ID" :
816 parms.Hostname = value;
819 throw new ArgumentException("Keyword not supported :"+name);
824 static bool IsValidDatabaseName (string database)
826 if (database.Length > 32 || database.Length < 1)
829 if (database[0] == '"' && database[database.Length] == '"')
830 database = database.Substring (1, database.Length - 2);
831 else if (Char.IsDigit (database[0]))
834 if (database[0] == '_')
837 foreach (char c in database.Substring (1, database.Length - 1))
838 if (!Char.IsLetterOrDigit (c) && c != '_')
843 private void OnSqlInfoMessage (SqlInfoMessageEventArgs value)
845 if (InfoMessage != null)
846 InfoMessage (this, value);
849 private void OnStateChange (StateChangeEventArgs value)
851 if (StateChange != null)
852 StateChange (this, value);
855 private sealed class SqlMonitorSocket : UdpClient
857 // UDP port that the SQL Monitor listens
858 private static readonly int SqlMonitorUdpPort = 1434;
859 private static readonly string SqlServerNotExist = "SQL Server does not exist or access denied";
861 private string server;
862 private string instance;
864 internal SqlMonitorSocket (string ServerName, string InstanceName)
865 : base (ServerName, SqlMonitorUdpPort)
868 instance = InstanceName;
871 internal int DiscoverTcpPort ()
873 int SqlServerTcpPort;
874 Client.Blocking = false;
875 // send command to UDP 1434 (SQL Monitor) to get
876 // the TCP port to connect to the MS SQL server
877 ASCIIEncoding enc = new ASCIIEncoding ();
878 Byte[] rawrq = new Byte [instance.Length + 1];
880 enc.GetBytes (instance, 0, instance.Length, rawrq, 1);
881 int bytes = Send (rawrq, rawrq.Length);
887 result = Client.Poll (100, SelectMode.SelectRead);
891 if (Client.Available <= 0)
894 IPEndPoint endpoint = new IPEndPoint (Dns.GetHostByName ("localhost").AddressList [0], 0);
897 rawrs = Receive (ref endpoint);
899 string rs = Encoding.ASCII.GetString (rawrs);
901 string[] rawtokens = rs.Split (';');
902 Hashtable data = new Hashtable ();
903 for (int i = 0; i < rawtokens.Length / 2 && i < 256; i++) {
904 data [rawtokens [i * 2]] = rawtokens [ i * 2 + 1];
906 if (!data.ContainsKey ("tcp"))
907 throw new NotImplementedException ("Only TCP/IP is supported.");
909 SqlServerTcpPort = int.Parse ((string) data ["tcp"]);
912 return SqlServerTcpPort;
916 #endregion // Methods