2 // Mono.Data.SybaseClient.SybaseConnection.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
12 using Mono.Data.Tds.Protocol;
14 using System.Collections;
15 using System.Collections.Specialized;
16 using System.ComponentModel;
18 using System.Data.Common;
19 using System.EnterpriseServices;
21 using System.Net.Sockets;
24 namespace Mono.Data.SybaseClient {
25 public sealed class SybaseConnection : Component, IDbConnection, ICloneable
28 bool disposed = false;
30 // The set of SQL connection pools
31 static TdsConnectionPoolManager sybaseConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds50);
33 // The current connection pool
34 TdsConnectionPool pool;
36 // The connection string that identifies this connection
37 string connectionString = null;
39 // The transaction object for the current transaction
40 SybaseTransaction transaction = null;
42 // Connection parameters
43 TdsConnectionParameters parms = new TdsConnectionParameters ();
47 int connectionTimeout;
54 ConnectionState state = ConnectionState.Closed;
56 SybaseDataReader dataReader = null;
65 public SybaseConnection ()
70 public SybaseConnection (string connectionString)
72 ConnectionString = connectionString;
75 #endregion // Constructors
79 public string ConnectionString {
80 get { return connectionString; }
81 set { SetConnectionString (value); }
84 public int ConnectionTimeout {
85 get { return connectionTimeout; }
88 public string Database {
89 get { return tds.Database; }
92 internal SybaseDataReader DataReader {
93 get { return dataReader; }
94 set { dataReader = value; }
97 public string DataSource {
98 get { return dataSource; }
101 public int PacketSize {
102 get { return packetSize; }
105 public string ServerVersion {
106 get { return tds.ServerVersion; }
109 public ConnectionState State {
110 get { return state; }
117 internal SybaseTransaction Transaction {
118 get { return transaction; }
119 set { transaction = value; }
122 public string WorkstationId {
123 get { return parms.Hostname; }
126 #endregion // Properties
128 #region Events and Delegates
130 public event SybaseInfoMessageEventHandler InfoMessage;
131 public event StateChangeEventHandler StateChange;
133 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
135 throw new SybaseException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SybaseClient Data Provider", e.State);
138 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
140 OnSybaseInfoMessage (CreateSybaseInfoMessageEvent (e.Errors));
143 #endregion // Events and Delegates
147 public SybaseTransaction BeginTransaction ()
149 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
152 public SybaseTransaction BeginTransaction (IsolationLevel iso)
154 return BeginTransaction (iso, String.Empty);
157 public SybaseTransaction BeginTransaction (string transactionName)
159 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
162 public SybaseTransaction BeginTransaction (IsolationLevel iso, string transactionName)
164 if (State == ConnectionState.Closed)
165 throw new InvalidOperationException ("The connection is not open.");
166 if (Transaction != null)
167 throw new InvalidOperationException ("SybaseConnection does not support parallel transactions.");
169 string isolevel = String.Empty;
171 case IsolationLevel.Chaos:
174 case IsolationLevel.ReadCommitted:
175 isolevel = "READ COMMITTED";
177 case IsolationLevel.ReadUncommitted:
178 isolevel = "READ UNCOMMITTED";
180 case IsolationLevel.RepeatableRead:
181 isolevel = "REPEATABLE READ";
183 case IsolationLevel.Serializable:
184 isolevel = "SERIALIZABLE";
188 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
189 transaction = new SybaseTransaction (this, iso);
193 public void ChangeDatabase (string database)
195 if (!IsValidDatabaseName (database))
196 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
197 if (State != ConnectionState.Open)
198 throw new InvalidOperationException ("The connection is not open");
199 tds.Execute (String.Format ("use {0}", database));
202 private void ChangeState (ConnectionState currentState)
204 ConnectionState originalState = state;
205 state = currentState;
206 OnStateChange (CreateStateChangeEvent (originalState, currentState));
211 if (Transaction != null && Transaction.IsOpen)
212 Transaction.Rollback ();
214 pool.ReleaseConnection (tds);
217 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
218 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
219 ChangeState (ConnectionState.Closed);
222 public SybaseCommand CreateCommand ()
224 SybaseCommand command = new SybaseCommand ();
225 command.Connection = this;
229 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
231 return new StateChangeEventArgs (originalState, currentState);
234 private SybaseInfoMessageEventArgs CreateSybaseInfoMessageEvent (TdsInternalErrorCollection errors)
236 return new SybaseInfoMessageEventArgs (errors);
239 protected override void Dispose (bool disposing)
243 if (State == ConnectionState.Open)
248 base.Dispose (disposing);
254 public void EnlistDistributedTransaction (ITransaction transaction)
256 throw new NotImplementedException ();
259 object ICloneable.Clone ()
261 return new SybaseConnection (ConnectionString);
264 IDbTransaction IDbConnection.BeginTransaction ()
266 return BeginTransaction ();
269 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
271 return BeginTransaction (iso);
274 IDbCommand IDbConnection.CreateCommand ()
276 return CreateCommand ();
279 void IDisposable.Dispose ()
282 GC.SuppressFinalize (this);
285 [MonoTODO ("Figure out the Sybase way to reset the connection.")]
288 string serverName = "";
289 if (connectionString == null || connectionString.Equals (""))
290 throw new InvalidOperationException ("Connection string has not been initialized.");
294 ParseDataSource (dataSource, out port, out serverName);
295 tds = new Tds50 (serverName, port, PacketSize, ConnectionTimeout);
298 ParseDataSource (dataSource, out port, out serverName);
299 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
300 pool = sybaseConnectionPools.GetConnectionPool (connectionString, info);
301 tds = pool.GetConnection ();
304 catch (TdsTimeoutException e) {
305 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
308 tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
309 tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
311 if (!tds.IsConnected) {
314 ChangeState (ConnectionState.Open);
315 ChangeDatabase (parms.Database);
319 pool.ReleaseConnection (tds);
323 else if (connectionReset) {
324 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
325 ChangeState (ConnectionState.Open);
329 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName)
335 if ((idx = theDataSource.IndexOf (",")) > -1) {
336 theServerName = theDataSource.Substring (0, idx);
337 string p = theDataSource.Substring (idx + 1);
338 thePort = Int32.Parse (p);
341 theServerName = theDataSource;
345 private string ParseValue (string name, string value)
347 if (name.Length == 0 && value.Length > 0)
348 throw new ArgumentException ("Expected '=' delimiter while parsing connection value pair.");
350 return value.Trim ();
354 private void SetConnectionString (string connectionString)
356 if (connectionString == String.Empty) {
357 this.connectionString = connectionString;
361 NameValueCollection parameters = new NameValueCollection ();
363 string name = String.Empty;
364 string value = String.Empty;
365 StringBuilder sb = new StringBuilder ();
367 char delimiter = '\0';
369 foreach (char c in connectionString) {
373 if (delimiter.Equals (c))
375 else if (delimiter.Equals ('\0'))
381 if (delimiter.Equals ('\0')) {
382 value = ParseValue (name, sb.ToString ());
383 if (!value.Equals (""))
384 parameters [name.ToUpper ().Trim ()] = value;
386 sb = new StringBuilder ();
392 if (delimiter.Equals ('\0')) {
393 name = sb.ToString ();
394 sb = new StringBuilder ();
405 if (!delimiter.Equals ('\0'))
406 throw new ArgumentException (String.Format ("Matching end delimiter {0} not found in connection option value.", delimiter));
408 value = ParseValue (name, sb.ToString ());
409 if (!value.Equals (""))
410 parameters [name.ToUpper ().Trim ()] = value;
412 if (this.ConnectionString == null)
413 SetDefaultConnectionParameters (parameters);
415 SetProperties (parameters);
417 this.connectionString = connectionString;
420 private void SetDefaultConnectionParameters (NameValueCollection parameters)
422 if (null == parameters.Get ("APPLICATION NAME"))
423 parameters["APPLICATION NAME"] = "Mono SybaseClient Data Provider";
424 if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
425 parameters["CONNECT TIMEOUT"] = "15";
426 if (null == parameters.Get ("CONNECTION LIFETIME"))
427 parameters["CONNECTION LIFETIME"] = "0";
428 if (null == parameters.Get ("CONNECTION RESET"))
429 parameters["CONNECTION RESET"] = "true";
430 if (null == parameters.Get ("ENLIST"))
431 parameters["ENLIST"] = "true";
432 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
433 parameters["INTEGRATED SECURITY"] = "false";
434 if (null == parameters.Get ("MAX POOL SIZE"))
435 parameters["MAX POOL SIZE"] = "100";
436 if (null == parameters.Get ("MIN POOL SIZE"))
437 parameters["MIN POOL SIZE"] = "0";
438 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
439 parameters["NETWORK LIBRARY"] = "dbmssocn";
440 if (null == parameters.Get ("PACKET SIZE"))
441 parameters["PACKET SIZE"] = "512";
442 if (null == parameters.Get ("PERSIST SECURITY INFO"))
443 parameters["PERSIST SECURITY INFO"] = "false";
444 if (null == parameters.Get ("POOLING"))
445 parameters["POOLING"] = "true";
446 if (null == parameters.Get ("WORKSTATION ID"))
447 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
450 private void SetProperties (NameValueCollection parameters)
453 foreach (string name in parameters) {
454 value = parameters [name];
457 case "APPLICATION NAME" :
458 parms.ApplicationName = value;
460 case "ATTACHDBFILENAME" :
461 case "EXTENDED PROPERTIES" :
462 case "INITIAL FILE NAME" :
464 case "CONNECT TIMEOUT" :
465 case "CONNECTION TIMEOUT" :
466 connectionTimeout = Int32.Parse (value);
468 case "CONNECTION LIFETIME" :
470 case "CONNECTION RESET" :
471 connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
473 case "CURRENT LANGUAGE" :
474 parms.Language = value;
480 case "NETWORK ADDRESS" :
485 case "INITIAL CATALOG" :
487 parms.Database = value;
489 case "INTEGRATED SECURITY" :
490 case "TRUSTED_CONNECTION" :
492 case "MAX POOL SIZE" :
493 maxPoolSize = Int32.Parse (value);
495 case "MIN POOL SIZE" :
496 minPoolSize = Int32.Parse (value);
499 case "NETWORK LIBRARY" :
500 if (!value.ToUpper ().Equals ("DBMSSOCN"))
501 throw new ArgumentException ("Unsupported network library.");
504 packetSize = Int32.Parse (value);
508 parms.Password = value;
510 case "PERSIST SECURITY INFO" :
513 pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
518 case "WORKSTATION ID" :
519 parms.Hostname = value;
525 private static bool IsValidDatabaseName (string database)
527 if (database.Length > 32 || database.Length < 1)
530 if (database[0] == '"' && database[database.Length] == '"')
531 database = database.Substring (1, database.Length - 2);
532 else if (Char.IsDigit (database[0]))
535 if (database[0] == '_')
538 foreach (char c in database.Substring (1, database.Length - 1))
539 if (!Char.IsLetterOrDigit (c) && c != '_')
544 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
546 if (InfoMessage != null)
547 InfoMessage (this, value);
550 private void OnStateChange (StateChangeEventArgs value)
552 if (StateChange != null)
553 StateChange (this, value);
556 #endregion // Methods