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
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.SybaseClient {
46 public sealed class SybaseConnection : Component, IDbConnection, ICloneable
49 bool disposed = false;
51 // The set of SQL connection pools
52 static TdsConnectionPoolManager sybaseConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds50);
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 SybaseTransaction transaction = null;
63 // Connection parameters
64 TdsConnectionParameters parms = new TdsConnectionParameters ();
68 int connectionTimeout;
75 ConnectionState state = ConnectionState.Closed;
77 SybaseDataReader dataReader = null;
86 public SybaseConnection ()
91 public SybaseConnection (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 SybaseDataReader 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 SybaseTransaction 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 SybaseInfoMessageEventHandler InfoMessage;
152 public event StateChangeEventHandler StateChange;
154 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
156 throw new SybaseException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SybaseClient Data Provider", e.State);
159 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
161 OnSybaseInfoMessage (CreateSybaseInfoMessageEvent (e.Errors));
164 #endregion // Events and Delegates
168 public SybaseTransaction BeginTransaction ()
170 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
173 public SybaseTransaction BeginTransaction (IsolationLevel iso)
175 return BeginTransaction (iso, String.Empty);
178 public SybaseTransaction BeginTransaction (string transactionName)
180 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
183 public SybaseTransaction 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 ("SybaseConnection 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 SybaseTransaction (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 SybaseCommand CreateCommand ()
245 SybaseCommand command = new SybaseCommand ();
246 command.Connection = this;
250 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
252 return new StateChangeEventArgs (originalState, currentState);
255 private SybaseInfoMessageEventArgs CreateSybaseInfoMessageEvent (TdsInternalErrorCollection errors)
257 return new SybaseInfoMessageEventArgs (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 SybaseConnection (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 Sybase way to reset the connection.")]
309 string serverName = "";
310 if (connectionString == null || connectionString.Equals (""))
311 throw new InvalidOperationException ("Connection string has not been initialized.");
315 ParseDataSource (dataSource, out port, out serverName);
316 tds = new Tds50 (serverName, port, PacketSize, ConnectionTimeout);
319 ParseDataSource (dataSource, out port, out serverName);
320 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
321 pool = sybaseConnectionPools.GetConnectionPool (connectionString, info);
322 tds = pool.GetConnection ();
325 catch (TdsTimeoutException e) {
326 throw SybaseException.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)
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 private string ParseValue (string name, string value)
368 if (name.Length == 0 && value.Length > 0)
369 throw new ArgumentException ("Expected '=' delimiter while parsing connection value pair.");
371 return value.Trim ();
375 private void SetConnectionString (string connectionString)
377 if (connectionString == String.Empty) {
378 this.connectionString = connectionString;
382 NameValueCollection parameters = new NameValueCollection ();
384 string name = String.Empty;
385 string value = String.Empty;
386 StringBuilder sb = new StringBuilder ();
388 char delimiter = '\0';
390 foreach (char c in connectionString) {
394 if (delimiter.Equals (c))
396 else if (delimiter.Equals ('\0'))
402 if (delimiter.Equals ('\0')) {
403 value = ParseValue (name, sb.ToString ());
404 if (!value.Equals (""))
405 parameters [name.ToUpper ().Trim ()] = value;
407 sb = new StringBuilder ();
413 if (delimiter.Equals ('\0')) {
414 name = sb.ToString ();
415 sb = new StringBuilder ();
426 if (!delimiter.Equals ('\0'))
427 throw new ArgumentException (String.Format ("Matching end delimiter {0} not found in connection option value.", delimiter));
429 value = ParseValue (name, sb.ToString ());
430 if (!value.Equals (""))
431 parameters [name.ToUpper ().Trim ()] = value;
433 SetDefaultConnectionParameters (parameters);
434 SetProperties (parameters);
436 this.connectionString = connectionString;
439 private void SetDefaultConnectionParameters (NameValueCollection parameters)
441 if (null == parameters.Get ("APPLICATION NAME"))
442 parameters["APPLICATION NAME"] = "Mono SybaseClient Data Provider";
443 if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT")) {
444 parameters["CONNECT TIMEOUT"] = "15";
445 connectionTimeout = 15;
447 if (null == parameters.Get ("CONNECTION LIFETIME"))
448 parameters["CONNECTION LIFETIME"] = "0";
449 if (null == parameters.Get ("CONNECTION RESET"))
450 parameters["CONNECTION RESET"] = "true";
451 if (null == parameters.Get ("ENLIST"))
452 parameters["ENLIST"] = "true";
453 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
454 parameters["INTEGRATED SECURITY"] = "false";
455 if (null == parameters.Get ("MAX POOL SIZE")) {
456 parameters["MAX POOL SIZE"] = "100";
459 if (null == parameters.Get ("MIN POOL SIZE")) {
460 parameters["MIN POOL SIZE"] = "0";
463 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
464 parameters["NETWORK LIBRARY"] = "dbmssocn";
465 if (null == parameters.Get ("PACKET SIZE")) {
466 parameters["PACKET SIZE"] = "512";
469 if (null == parameters.Get ("PERSIST SECURITY INFO"))
470 parameters["PERSIST SECURITY INFO"] = "false";
471 if (null == parameters.Get ("POOLING"))
472 parameters["POOLING"] = "true";
473 if (null == parameters.Get ("WORKSTATION ID"))
474 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
477 private void SetProperties (NameValueCollection parameters)
480 foreach (string name in parameters) {
481 value = parameters [name];
484 case "APPLICATION NAME" :
485 parms.ApplicationName = value;
487 case "ATTACHDBFILENAME" :
488 case "EXTENDED PROPERTIES" :
489 case "INITIAL FILE NAME" :
491 case "CONNECT TIMEOUT" :
492 case "CONNECTION TIMEOUT" :
493 connectionTimeout = Int32.Parse (value);
495 case "CONNECTION LIFETIME" :
497 case "CONNECTION RESET" :
498 connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
500 case "CURRENT LANGUAGE" :
501 parms.Language = value;
507 case "NETWORK ADDRESS" :
512 case "INITIAL CATALOG" :
514 parms.Database = value;
516 case "INTEGRATED SECURITY" :
517 case "TRUSTED_CONNECTION" :
519 case "MAX POOL SIZE" :
520 maxPoolSize = Int32.Parse (value);
522 case "MIN POOL SIZE" :
523 minPoolSize = Int32.Parse (value);
526 case "NETWORK LIBRARY" :
527 if (!value.ToUpper ().Equals ("DBMSSOCN"))
528 throw new ArgumentException ("Unsupported network library.");
531 packetSize = Int32.Parse (value);
535 parms.Password = value;
537 case "PERSIST SECURITY INFO" :
540 pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
545 case "WORKSTATION ID" :
546 parms.Hostname = value;
552 private static bool IsValidDatabaseName (string database)
554 if (database.Length > 32 || database.Length < 1)
557 if (database[0] == '"' && database[database.Length] == '"')
558 database = database.Substring (1, database.Length - 2);
559 else if (Char.IsDigit (database[0]))
562 if (database[0] == '_')
565 foreach (char c in database.Substring (1, database.Length - 1))
566 if (!Char.IsLetterOrDigit (c) && c != '_')
571 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
573 if (InfoMessage != null)
574 InfoMessage (this, value);
577 private void OnStateChange (StateChangeEventArgs value)
579 if (StateChange != null)
580 StateChange (this, value);
583 #endregion // Methods