2 // Mono.Data.TdsClient.TdsConnection.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
8 // Copyright (C) Tim Coleman, 2002, 2003
9 // Copyright (C) Daniel Morgan, 2003
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using Mono.Data.Tds.Protocol;
35 using System.Collections;
36 using System.Collections.Specialized;
37 using System.ComponentModel;
39 using System.Data.Common;
40 using System.EnterpriseServices;
42 using System.Net.Sockets;
45 namespace Mono.Data.TdsClient {
46 public sealed class TdsConnection : Component, IDbConnection, ICloneable
49 bool disposed = false;
51 // The set of SQL connection pools
52 static TdsConnectionPoolManager tdsConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds42);
54 // The current connection pool
55 TdsConnectionPool pool;
57 // The connection string that identifies this connection
58 string connectionString = null;
60 // The transaction object for the current transaction
61 TdsTransaction transaction = null;
63 // Connection parameters
64 TdsConnectionParameters parms = new TdsConnectionParameters ();
68 int connectionTimeout;
75 ConnectionState state = ConnectionState.Closed;
77 TdsDataReader dataReader = null;
86 public TdsConnection ()
91 public TdsConnection (string connectionString)
93 ConnectionString = connectionString;
96 #endregion // Constructors
100 public string ConnectionString {
101 get { return connectionString; }
102 set { SetConnectionString (value); }
105 public int ConnectionTimeout {
106 get { return connectionTimeout; }
109 public string Database {
110 get { return tds.Database; }
113 internal TdsDataReader DataReader {
114 get { return dataReader; }
115 set { dataReader = value; }
118 public string DataSource {
119 get { return dataSource; }
122 public int PacketSize {
123 get { return packetSize; }
126 public string ServerVersion {
127 get { return tds.ServerVersion; }
130 public ConnectionState State {
131 get { return state; }
138 internal TdsTransaction Transaction {
139 get { return transaction; }
140 set { transaction = value; }
143 public string WorkstationId {
144 get { return parms.Hostname; }
147 #endregion // Properties
149 #region Events and Delegates
151 public event TdsInfoMessageEventHandler InfoMessage;
152 public event StateChangeEventHandler StateChange;
154 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
156 throw new TdsException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono TdsClient Data Provider", e.State);
159 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
161 OnTdsInfoMessage (CreateTdsInfoMessageEvent (e.Errors));
164 #endregion // Events and Delegates
168 public TdsTransaction BeginTransaction ()
170 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
173 public TdsTransaction BeginTransaction (IsolationLevel iso)
175 return BeginTransaction (iso, String.Empty);
178 public TdsTransaction BeginTransaction (string transactionName)
180 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
183 public TdsTransaction BeginTransaction (IsolationLevel iso, string transactionName)
185 if (State == ConnectionState.Closed)
186 throw new InvalidOperationException ("The connection is not open.");
187 if (Transaction != null)
188 throw new InvalidOperationException ("TdsConnection does not support parallel transactions.");
190 string isolevel = String.Empty;
192 case IsolationLevel.Chaos:
195 case IsolationLevel.ReadCommitted:
196 isolevel = "READ COMMITTED";
198 case IsolationLevel.ReadUncommitted:
199 isolevel = "READ UNCOMMITTED";
201 case IsolationLevel.RepeatableRead:
202 isolevel = "REPEATABLE READ";
204 case IsolationLevel.Serializable:
205 isolevel = "SERIALIZABLE";
209 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
210 transaction = new TdsTransaction (this, iso);
214 public void ChangeDatabase (string database)
216 if (!IsValidDatabaseName (database))
217 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
218 if (State != ConnectionState.Open)
219 throw new InvalidOperationException ("The connection is not open");
220 tds.Execute (String.Format ("use {0}", database));
223 private void ChangeState (ConnectionState currentState)
225 ConnectionState originalState = state;
226 state = currentState;
227 OnStateChange (CreateStateChangeEvent (originalState, currentState));
232 if (Transaction != null && Transaction.IsOpen)
233 Transaction.Rollback ();
235 pool.ReleaseConnection (tds);
238 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
239 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
240 ChangeState (ConnectionState.Closed);
243 public TdsCommand CreateCommand ()
245 TdsCommand command = new TdsCommand ();
246 command.Connection = this;
250 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
252 return new StateChangeEventArgs (originalState, currentState);
255 private TdsInfoMessageEventArgs CreateTdsInfoMessageEvent (TdsInternalErrorCollection errors)
257 return new TdsInfoMessageEventArgs (errors);
260 protected override void Dispose (bool disposing)
264 if (State == ConnectionState.Open)
269 base.Dispose (disposing);
275 public void EnlistDistributedTransaction (ITransaction transaction)
277 throw new NotImplementedException ();
280 object ICloneable.Clone ()
282 return new TdsConnection (ConnectionString);
285 IDbTransaction IDbConnection.BeginTransaction ()
287 return BeginTransaction ();
290 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
292 return BeginTransaction (iso);
295 IDbCommand IDbConnection.CreateCommand ()
297 return CreateCommand ();
300 void IDisposable.Dispose ()
303 GC.SuppressFinalize (this);
306 [MonoTODO ("Figure out the Tds way to reset the connection.")]
309 string serverName = "";
310 if (connectionString == null)
311 throw new InvalidOperationException ("Connection string has not been initialized.");
315 ParseDataSource (dataSource, out port, out serverName);
316 tds = new Tds42 (serverName, port, PacketSize, ConnectionTimeout);
319 ParseDataSource (dataSource, out port, out serverName);
320 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
321 pool = tdsConnectionPools.GetConnectionPool (connectionString, info);
322 tds = pool.GetConnection ();
325 catch (TdsTimeoutException e) {
326 throw TdsException.FromTdsInternalException ((TdsInternalException) e);
329 tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
330 tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
332 if (!tds.IsConnected) {
335 ChangeState (ConnectionState.Open);
336 ChangeDatabase (parms.Database);
340 pool.ReleaseConnection (tds);
344 else if (connectionReset) {
345 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
346 ChangeState (ConnectionState.Open);
350 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName)
353 thePort = 1433; // default TCP port for SQL Server
356 if ((idx = theDataSource.IndexOf (",")) > -1) {
357 theServerName = theDataSource.Substring (0, idx);
358 string p = theDataSource.Substring (idx + 1);
359 thePort = Int32.Parse (p);
362 theServerName = theDataSource;
366 void SetConnectionString (string connectionString)
368 connectionString += ";";
369 NameValueCollection parameters = new NameValueCollection ();
371 if (connectionString == String.Empty)
374 bool inQuote = false;
375 bool inDQuote = false;
377 string name = String.Empty;
378 string value = String.Empty;
379 StringBuilder sb = new StringBuilder ();
381 foreach (char c in connectionString)
388 inDQuote = !inDQuote;
391 if (!inDQuote && !inQuote) {
392 if (name != String.Empty && name != null) {
393 value = sb.ToString ();
394 parameters [name.ToUpper ().Trim ()] = value.Trim ();
397 value = String.Empty;
398 sb = new StringBuilder ();
404 if (!inDQuote && !inQuote) {
405 name = sb.ToString ();
406 sb = new StringBuilder ();
417 if (this.ConnectionString == null)
419 SetDefaultConnectionParameters (parameters);
422 SetProperties (parameters);
424 this.connectionString = connectionString;
427 void SetDefaultConnectionParameters (NameValueCollection parameters)
429 if (null == parameters.Get ("APPLICATION NAME"))
430 parameters["APPLICATION NAME"] = ".Net TdsClient Data Provider";
431 if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
432 parameters["CONNECT TIMEOUT"] = "15";
433 if (null == parameters.Get ("CONNECTION LIFETIME"))
434 parameters["CONNECTION LIFETIME"] = "0";
435 if (null == parameters.Get ("CONNECTION RESET"))
436 parameters["CONNECTION RESET"] = "true";
437 if (null == parameters.Get ("ENLIST"))
438 parameters["ENLIST"] = "true";
439 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
440 parameters["INTEGRATED SECURITY"] = "false";
441 if (null == parameters.Get ("MAX POOL SIZE"))
442 parameters["MAX POOL SIZE"] = "100";
443 if (null == parameters.Get ("MIN POOL SIZE"))
444 parameters["MIN POOL SIZE"] = "0";
445 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
446 parameters["NETWORK LIBRARY"] = "dbmssocn";
447 if (null == parameters.Get ("PACKET SIZE"))
448 parameters["PACKET SIZE"] = "512";
449 if (null == parameters.Get ("PERSIST SECURITY INFO"))
450 parameters["PERSIST SECURITY INFO"] = "false";
451 if (null == parameters.Get ("POOLING"))
452 parameters["POOLING"] = "true";
453 if (null == parameters.Get ("WORKSTATION ID"))
454 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
457 private void SetProperties (NameValueCollection parameters)
460 foreach (string name in parameters) {
461 value = parameters[name];
464 case "APPLICATION NAME" :
465 parms.ApplicationName = value;
467 case "ATTACHDBFILENAME" :
468 case "EXTENDED PROPERTIES" :
469 case "INITIAL FILE NAME" :
471 case "CONNECT TIMEOUT" :
472 case "CONNECTION TIMEOUT" :
473 connectionTimeout = Int32.Parse (value);
475 case "CONNECTION LIFETIME" :
477 case "CONNECTION RESET" :
478 connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
480 case "CURRENT LANGUAGE" :
481 parms.Language = value;
487 case "NETWORK ADDRESS" :
492 case "INITIAL CATALOG" :
494 parms.Database = value;
496 case "INTEGRATED SECURITY" :
497 case "TRUSTED_CONNECTION" :
499 case "MAX POOL SIZE" :
500 maxPoolSize = Int32.Parse (value);
502 case "MIN POOL SIZE" :
503 minPoolSize = Int32.Parse (value);
506 case "NETWORK LIBRARY" :
507 if (!value.ToUpper ().Equals ("DBMSSOCN"))
508 throw new ArgumentException ("Unsupported network library.");
511 packetSize = Int32.Parse (value);
515 parms.Password = value;
517 case "PERSIST SECURITY INFO" :
520 pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
525 case "WORKSTATION ID" :
526 parms.Hostname = value;
533 static bool IsValidDatabaseName (string database)
535 if (database.Length > 32 || database.Length < 1)
538 if (database[0] == '"' && database[database.Length] == '"')
539 database = database.Substring (1, database.Length - 2);
540 else if (Char.IsDigit (database[0]))
543 if (database[0] == '_')
546 foreach (char c in database.Substring (1, database.Length - 1))
547 if (!Char.IsLetterOrDigit (c) && c != '_')
552 private void OnTdsInfoMessage (TdsInfoMessageEventArgs value)
554 if (InfoMessage != null)
555 InfoMessage (this, value);
558 private void OnStateChange (StateChangeEventArgs value)
560 if (StateChange != null)
561 StateChange (this, value);
564 #endregion // Methods