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;
48 using System.EnterpriseServices;
49 using System.Globalization;
51 using System.Net.Sockets;
55 namespace System.Data.SqlClient {
56 [DefaultEvent ("InfoMessage")]
57 public sealed class SqlConnection : Component, IDbConnection, ICloneable
60 bool disposed = false;
62 // The set of SQL connection pools
63 static TdsConnectionPoolManager sqlConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds70);
65 // The current connection pool
66 TdsConnectionPool pool;
68 // The connection string that identifies this connection
69 string connectionString = null;
71 // The transaction object for the current transaction
72 SqlTransaction transaction = null;
74 // Connection parameters
75 TdsConnectionParameters parms = new TdsConnectionParameters ();
79 int connectionTimeout;
86 ConnectionState state = ConnectionState.Closed;
88 SqlDataReader dataReader = null;
89 XmlReader xmlReader = null;
98 public SqlConnection ()
103 public SqlConnection (string connectionString)
105 ConnectionString = connectionString;
108 #endregion // Constructors
112 [DataCategory ("Data")]
113 [DataSysDescription ("Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'.")]
115 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
116 [RecommendedAsConfigurable (true)]
117 [RefreshProperties (RefreshProperties.All)]
118 [MonoTODO("persist security info, encrypt, enlist and , attachdbfilename keyword not implemented")]
119 public string ConnectionString {
120 get { return connectionString; }
121 set { SetConnectionString (value); }
124 [DataSysDescription ("Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.")]
125 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
126 public int ConnectionTimeout {
127 get { return connectionTimeout; }
130 [DataSysDescription ("Current SQL Server database, 'Initial Catalog=X' in the ConnectionString.")]
131 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
132 public string Database {
133 get { return tds.Database; }
136 internal SqlDataReader DataReader {
137 get { return dataReader; }
138 set { dataReader = value; }
141 [DataSysDescription ("Current SqlServer that the connection is opened to, 'Data Source=X' in the ConnectionString.")]
142 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
143 public string DataSource {
144 get { return dataSource; }
147 [DataSysDescription ("Network packet size, 'Packet Size=x' in the ConnectionString.")]
148 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
149 public int PacketSize {
150 get { return packetSize; }
154 [DataSysDescription ("Version of the SQL Server accessed by the SqlConnection.")]
155 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
156 public string ServerVersion {
157 get { return tds.ServerVersion; }
161 [DataSysDescription ("The ConnectionState indicating whether the connection is open or closed.")]
162 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
163 public ConnectionState State {
164 get { return state; }
171 internal SqlTransaction Transaction {
172 get { return transaction; }
173 set { transaction = value; }
176 [DataSysDescription ("Workstation Id, 'Workstation Id=x' in the ConnectionString.")]
177 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
178 public string WorkstationId {
179 get { return parms.Hostname; }
182 internal XmlReader XmlReader {
183 get { return xmlReader; }
184 set { xmlReader = value; }
187 #endregion // Properties
191 [DataCategory ("InfoMessage")]
192 [DataSysDescription ("Event triggered when messages arrive from the DataSource.")]
193 public event SqlInfoMessageEventHandler InfoMessage;
195 [DataCategory ("StateChange")]
196 [DataSysDescription ("Event triggered when the connection changes state.")]
197 public event StateChangeEventHandler StateChange;
203 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
205 throw new SqlException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SqlClient Data Provider", e.State);
208 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
210 OnSqlInfoMessage (CreateSqlInfoMessageEvent (e.Errors));
213 #endregion // Delegates
217 public SqlTransaction BeginTransaction ()
219 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
222 public SqlTransaction BeginTransaction (IsolationLevel iso)
224 return BeginTransaction (iso, String.Empty);
227 public SqlTransaction BeginTransaction (string transactionName)
229 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
232 public SqlTransaction BeginTransaction (IsolationLevel iso, string transactionName)
234 if (state == ConnectionState.Closed)
235 throw new InvalidOperationException ("The connection is not open.");
236 if (transaction != null)
237 throw new InvalidOperationException ("SqlConnection does not support parallel transactions.");
239 if (iso == IsolationLevel.Chaos)
240 throw new ArgumentException ("Invalid IsolationLevel parameter: must be ReadCommitted, ReadUncommitted, RepeatableRead, or Serializable.");
242 string isolevel = String.Empty;
244 case IsolationLevel.ReadCommitted:
245 isolevel = "READ COMMITTED";
247 case IsolationLevel.ReadUncommitted:
248 isolevel = "READ UNCOMMITTED";
250 case IsolationLevel.RepeatableRead:
251 isolevel = "REPEATABLE READ";
253 case IsolationLevel.Serializable:
254 isolevel = "SERIALIZABLE";
258 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0};BEGIN TRANSACTION {1}", isolevel, transactionName));
260 transaction = new SqlTransaction (this, iso);
264 public void ChangeDatabase (string database)
266 if (!IsValidDatabaseName (database))
267 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
268 if (state != ConnectionState.Open)
269 throw new InvalidOperationException ("The connection is not open.");
270 tds.Execute (String.Format ("use {0}", database));
273 private void ChangeState (ConnectionState currentState)
275 ConnectionState originalState = state;
276 state = currentState;
277 OnStateChange (CreateStateChangeEvent (originalState, currentState));
282 if (transaction != null && transaction.IsOpen)
283 transaction.Rollback ();
285 if (dataReader != null || xmlReader != null) {
286 if(tds != null) tds.SkipToEnd ();
292 if(pool != null) pool.ReleaseConnection (tds);
294 if(tds != null) tds.Disconnect ();
297 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
298 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
301 ChangeState (ConnectionState.Closed);
304 public SqlCommand CreateCommand ()
306 SqlCommand command = new SqlCommand ();
307 command.Connection = this;
311 private SqlInfoMessageEventArgs CreateSqlInfoMessageEvent (TdsInternalErrorCollection errors)
313 return new SqlInfoMessageEventArgs (errors);
316 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
318 return new StateChangeEventArgs (originalState, currentState);
321 protected override void Dispose (bool disposing)
325 if (State == ConnectionState.Open)
330 base.Dispose (disposing);
335 [MonoTODO ("Not sure what this means at present.")]
336 public void EnlistDistributedTransaction (ITransaction transaction)
338 throw new NotImplementedException ();
341 object ICloneable.Clone ()
343 return new SqlConnection (ConnectionString);
346 IDbTransaction IDbConnection.BeginTransaction ()
348 return BeginTransaction ();
351 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
353 return BeginTransaction (iso);
356 IDbCommand IDbConnection.CreateCommand ()
358 return CreateCommand ();
361 void IDisposable.Dispose ()
364 GC.SuppressFinalize (this);
369 string serverName = "";
370 if (connectionString == null)
371 throw new InvalidOperationException ("Connection string has not been initialized.");
375 if(!ParseDataSource (dataSource, out port, out serverName))
376 throw new SqlException(20, 0, "SQL Server does not exist or access denied.", 17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
377 tds = new Tds70 (serverName, port, PacketSize, ConnectionTimeout);
380 if(!ParseDataSource (dataSource, out port, out serverName))
381 throw new SqlException(20, 0, "SQL Server does not exist or access denied.", 17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
383 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
384 pool = sqlConnectionPools.GetConnectionPool (connectionString, info);
385 tds = pool.GetConnection ();
388 catch (TdsTimeoutException e) {
389 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
392 tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
393 tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
395 if (!tds.IsConnected) {
401 pool.ReleaseConnection (tds);
406 /* Not sure ebout removing these 2 lines.
407 * The command that gets to the sql server is just
408 * 'sp_reset_connection' and it fails.
409 * Either remove them definitely or fix it
410 else if (connectionReset)
411 tds.ExecProc ("sp_reset_connection");
414 ChangeState (ConnectionState.Open);
417 private bool ParseDataSource (string theDataSource, out int thePort, out string theServerName)
420 string theInstanceName = "";
421 if ((theDataSource == null) || (theServerName == null))
422 throw new ArgumentException("Format of initialization string doesnot conform to specifications");
424 thePort = 1433; // default TCP port for SQL Server
428 if ((idx = theDataSource.IndexOf (",")) > -1) {
429 theServerName = theDataSource.Substring (0, idx);
430 string p = theDataSource.Substring (idx + 1);
431 thePort = Int32.Parse (p);
433 else if ((idx = theDataSource.IndexOf ("\\")) > -1) {
434 theServerName = theDataSource.Substring (0, idx);
435 theInstanceName = theDataSource.Substring (idx + 1);
436 // do port discovery via UDP port 1434
437 port = DiscoverTcpPortViaSqlMonitor (theServerName, theInstanceName);
442 theServerName = theDataSource;
445 if(theServerName.Equals("(local)"))
446 theServerName = "localhost";
451 private bool ConvertIntegratedSecurity (string value)
453 if (value.ToUpper() == "SSPI")
458 return ConvertToBoolean("integrated security", value);
461 private bool ConvertToBoolean(string key, string value)
463 string upperValue = value.ToUpper();
465 if (upperValue == "TRUE" ||upperValue == "YES")
469 else if (upperValue == "FALSE" || upperValue == "NO")
474 throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
475 "Invalid value \"{0}\" for key '{1}'.", value, key));
478 private int ConvertToInt32(string key, string value)
482 return int.Parse(value);
486 throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
487 "Invalid value \"{0}\" for key '{1}'.", value, key));
491 private int DiscoverTcpPortViaSqlMonitor(string ServerName, string InstanceName)
493 SqlMonitorSocket msock;
494 msock = new SqlMonitorSocket (ServerName, InstanceName);
495 int SqlServerPort = msock.DiscoverTcpPort ();
497 return SqlServerPort;
500 void SetConnectionString (string connectionString)
502 NameValueCollection parameters = new NameValueCollection ();
504 if (( connectionString == null)||( connectionString.Length == 0))
506 connectionString += ";";
508 bool inQuote = false;
509 bool inDQuote = false;
512 string name = String.Empty;
513 string value = String.Empty;
514 StringBuilder sb = new StringBuilder ();
516 for (int i = 0; i < connectionString.Length; i += 1) {
517 char c = connectionString [i];
519 if (i == connectionString.Length - 1)
522 peek = connectionString [i + 1];
528 else if (peek.Equals (c)) {
538 else if (peek.Equals (c)) {
543 inDQuote = !inDQuote;
546 if (inDQuote || inQuote)
549 if (name != String.Empty && name != null) {
550 value = sb.ToString ();
551 parameters [name.ToUpper ().Trim ()] = value.Trim ();
555 value = String.Empty;
556 sb = new StringBuilder ();
560 if (inDQuote || inQuote || !inName)
562 else if (peek.Equals (c)) {
567 name = sb.ToString ();
568 sb = new StringBuilder ();
573 if (inQuote || inDQuote)
575 else if (sb.Length > 0 && !peek.Equals (';'))
584 if (this.ConnectionString == null)
586 SetDefaultConnectionParameters (parameters);
589 SetProperties (parameters);
591 this.connectionString = connectionString;
594 void SetDefaultConnectionParameters (NameValueCollection parameters)
596 if (null == parameters.Get ("APPLICATION NAME") && null == parameters.Get ("APP"))
597 parameters["APPLICATION NAME"] = "Mono SqlClient Data Provider";
598 if (null == parameters.Get ("TIMEOUT") && null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
599 parameters["CONNECT TIMEOUT"] = "15";
600 if (null == parameters.Get ("CONNECTION LIFETIME"))
601 parameters["CONNECTION LIFETIME"] = "0";
602 if (null == parameters.Get ("CONNECTION RESET"))
603 parameters["CONNECTION RESET"] = "true";
604 if (null == parameters.Get ("ENLIST"))
605 parameters["ENLIST"] = "true";
606 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
607 parameters["INTEGRATED SECURITY"] = "false";
608 if (null == parameters.Get ("MAX POOL SIZE"))
609 parameters["MAX POOL SIZE"] = "100";
610 if (null == parameters.Get ("MIN POOL SIZE"))
611 parameters["MIN POOL SIZE"] = "0";
612 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET") && null == parameters.Get ("NETWORK"))
613 parameters["NETWORK LIBRARY"] = "dbmssocn";
614 if (null == parameters.Get ("PACKET SIZE"))
615 parameters["PACKET SIZE"] = "512";
616 if (null == parameters.Get ("PERSIST SECURITY INFO") && null == parameters.Get ("PERSISTSECURITYINFO"))
617 parameters["PERSIST SECURITY INFO"] = "false";
618 if (null == parameters.Get ("POOLING"))
619 parameters["POOLING"] = "true";
620 if (null == parameters.Get ("WORKSTATION ID") && null == parameters.Get ("WSID"))
621 parameters["WORKSTATION ID"] = Dns.GetHostName();
624 private void SetProperties (NameValueCollection parameters)
626 foreach (string name in parameters) {
627 string value = parameters[name];
631 case "APPLICATION NAME" :
632 parms.ApplicationName = value;
634 case "ATTACHDBFILENAME" :
635 case "EXTENDED PROPERTIES" :
636 case "INITIAL FILE NAME" :
637 throw new NotImplementedException("Attachable database support is not implemented.");
639 case "CONNECT TIMEOUT" :
640 case "CONNECTION TIMEOUT" :
641 connectionTimeout = ConvertToInt32 ("connection timeout", value);
643 case "CONNECTION LIFETIME" :
645 case "CONNECTION RESET" :
646 connectionReset = ConvertToBoolean ("connection reset", value);
649 case "CURRENT LANGUAGE" :
650 parms.Language = value;
656 case "NETWORK ADDRESS" :
660 if (ConvertToBoolean("encrypt", value))
662 throw new NotImplementedException("SSL encryption for"
663 + " data sent between client and server is not"
668 if (!ConvertToBoolean("enlist", value))
670 throw new NotImplementedException("Disabling the automatic"
671 + " enlistment of connections in the thread's current"
672 + " transaction context is not implemented.");
675 case "INITIAL CATALOG" :
677 parms.Database = value;
679 case "INTEGRATED SECURITY" :
680 case "TRUSTED_CONNECTION" :
681 parms.DomainLogin = ConvertIntegratedSecurity(value);
683 case "MAX POOL SIZE" :
684 maxPoolSize = ConvertToInt32 ("max pool size", value);
686 case "MIN POOL SIZE" :
687 minPoolSize = ConvertToInt32 ("min pool size", value);
690 case "MULTIPLEACTIVERESULTSETS":
695 case "NETWORK LIBRARY" :
696 if (!value.ToUpper ().Equals ("DBMSSOCN"))
697 throw new ArgumentException ("Unsupported network library.");
700 packetSize = ConvertToInt32 ("packet size", value);
704 parms.Password = value;
706 case "PERSISTSECURITYINFO" :
707 case "PERSIST SECURITY INFO" :
708 // FIXME : not implemented
711 pooling = ConvertToBoolean("pooling", value);
719 case "WORKSTATION ID" :
720 parms.Hostname = value;
723 throw new ArgumentException("Keyword not supported :"+name);
728 static bool IsValidDatabaseName (string database)
730 if (database.Length > 32 || database.Length < 1)
733 if (database[0] == '"' && database[database.Length] == '"')
734 database = database.Substring (1, database.Length - 2);
735 else if (Char.IsDigit (database[0]))
738 if (database[0] == '_')
741 foreach (char c in database.Substring (1, database.Length - 1))
742 if (!Char.IsLetterOrDigit (c) && c != '_')
747 private void OnSqlInfoMessage (SqlInfoMessageEventArgs value)
749 if (InfoMessage != null)
750 InfoMessage (this, value);
753 private void OnStateChange (StateChangeEventArgs value)
755 if (StateChange != null)
756 StateChange (this, value);
759 private sealed class SqlMonitorSocket : UdpClient
761 // UDP port that the SQL Monitor listens
762 private static readonly int SqlMonitorUdpPort = 1434;
763 private static readonly string SqlServerNotExist = "SQL Server does not exist or access denied";
765 private string server;
766 private string instance;
768 internal SqlMonitorSocket (string ServerName, string InstanceName)
769 : base (ServerName, SqlMonitorUdpPort)
772 instance = InstanceName;
775 internal int DiscoverTcpPort ()
777 int SqlServerTcpPort;
778 Client.Blocking = false;
779 // send command to UDP 1434 (SQL Monitor) to get
780 // the TCP port to connect to the MS SQL server
781 ASCIIEncoding enc = new ASCIIEncoding ();
782 Byte[] rawrq = new Byte [instance.Length + 1];
784 enc.GetBytes (instance, 0, instance.Length, rawrq, 1);
785 int bytes = Send (rawrq, rawrq.Length);
791 result = Client.Poll (100, SelectMode.SelectRead);
795 if (Client.Available <= 0)
798 IPEndPoint endpoint = new IPEndPoint (Dns.GetHostByName ("localhost").AddressList [0], 0);
801 rawrs = Receive (ref endpoint);
803 string rs = Encoding.ASCII.GetString (rawrs);
805 string[] rawtokens = rs.Split (';');
806 Hashtable data = new Hashtable ();
807 for (int i = 0; i < rawtokens.Length / 2 && i < 256; i++) {
808 data [rawtokens [i * 2]] = rawtokens [ i * 2 + 1];
810 if (!data.ContainsKey ("tcp"))
811 throw new NotImplementedException ("Only TCP/IP is supported.");
813 SqlServerTcpPort = int.Parse ((string) data ["tcp"]);
816 return SqlServerTcpPort;
820 #endregion // Methods