**** Merged from MCS ****
[mono-project.git] / mcs / class / Mono.Data.SybaseClient / Mono.Data.SybaseClient / SybaseConnection.cs
blobc85f4ce3b34d2e9271c3731f6c9c0394d2daaa5e
1 //
2 // Mono.Data.SybaseClient.SybaseConnection.cs
3 //
4 // Author:
5 // Tim Coleman (tim@timcoleman.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
7 //
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:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
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;
34 using System;
35 using System.Collections;
36 using System.Collections.Specialized;
37 using System.ComponentModel;
38 using System.Data;
39 using System.Data.Common;
40 using System.EnterpriseServices;
41 using System.Net;
42 using System.Net.Sockets;
43 using System.Text;
45 namespace Mono.Data.SybaseClient {
46 public sealed class SybaseConnection : Component, IDbConnection, ICloneable
48 #region Fields
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 ();
65 bool connectionReset;
66 bool pooling;
67 string dataSource;
68 int connectionTimeout;
69 int minPoolSize;
70 int maxPoolSize;
71 int packetSize;
72 int port = 2048;
74 // The current state
75 ConnectionState state = ConnectionState.Closed;
77 SybaseDataReader dataReader = null;
79 // The TDS object
80 ITds tds;
82 #endregion // Fields
84 #region Constructors
86 public SybaseConnection ()
87 : this (String.Empty)
91 public SybaseConnection (string connectionString)
93 ConnectionString = connectionString;
96 #endregion // Constructors
98 #region Properties
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; }
134 internal ITds Tds {
135 get { return tds; }
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
166 #region Methods
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;
191 switch (iso) {
192 case IsolationLevel.Chaos:
193 isolevel = "CHAOS";
194 break;
195 case IsolationLevel.ReadCommitted:
196 isolevel = "READ COMMITTED";
197 break;
198 case IsolationLevel.ReadUncommitted:
199 isolevel = "READ UNCOMMITTED";
200 break;
201 case IsolationLevel.RepeatableRead:
202 isolevel = "REPEATABLE READ";
203 break;
204 case IsolationLevel.Serializable:
205 isolevel = "SERIALIZABLE";
206 break;
209 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
210 transaction = new SybaseTransaction (this, iso);
211 return transaction;
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));
230 public void Close ()
232 if (Transaction != null && Transaction.IsOpen)
233 Transaction.Rollback ();
234 if (pooling)
235 pool.ReleaseConnection (tds);
236 else
237 tds.Disconnect ();
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;
247 return command;
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)
262 if (!disposed) {
263 if (disposing) {
264 if (State == ConnectionState.Open)
265 Close ();
266 parms = null;
267 dataSource = null;
269 base.Dispose (disposing);
270 disposed = true;
274 [MonoTODO]
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 ()
302 Dispose (true);
303 GC.SuppressFinalize (this);
306 [MonoTODO ("Figure out the Sybase way to reset the connection.")]
307 public void Open ()
309 string serverName = "";
310 if (connectionString == null || connectionString.Equals (""))
311 throw new InvalidOperationException ("Connection string has not been initialized.");
313 try {
314 if (!pooling) {
315 ParseDataSource (dataSource, out port, out serverName);
316 tds = new Tds50 (serverName, port, PacketSize, ConnectionTimeout);
318 else {
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) {
333 try {
334 tds.Connect (parms);
335 ChangeState (ConnectionState.Open);
336 ChangeDatabase (parms.Database);
338 catch {
339 if (pooling)
340 pool.ReleaseConnection (tds);
341 throw;
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)
352 theServerName = "";
353 thePort = 2048;
355 int idx = 0;
356 if ((idx = theDataSource.IndexOf (",")) > -1) {
357 theServerName = theDataSource.Substring (0, idx);
358 string p = theDataSource.Substring (idx + 1);
359 thePort = Int32.Parse (p);
361 else {
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.");
370 if (name.Length > 0)
371 return value.Trim ();
372 return String.Empty;
375 private void SetConnectionString (string connectionString)
377 if (connectionString == String.Empty) {
378 this.connectionString = connectionString;
379 return;
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) {
391 switch (c) {
392 case '\'' :
393 case '"' :
394 if (delimiter.Equals (c))
395 delimiter = '\0';
396 else if (delimiter.Equals ('\0'))
397 delimiter = c;
398 else
399 sb.Append (c);
400 break;
401 case ';' :
402 if (delimiter.Equals ('\0')) {
403 value = ParseValue (name, sb.ToString ());
404 if (!value.Equals (""))
405 parameters [name.ToUpper ().Trim ()] = value;
406 name = String.Empty;
407 sb = new StringBuilder ();
409 else
410 sb.Append (c);
411 break;
412 case '=' :
413 if (delimiter.Equals ('\0')) {
414 name = sb.ToString ();
415 sb = new StringBuilder ();
417 else
418 sb.Append (c);
419 break;
420 default:
421 sb.Append (c);
422 break;
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 if (this.ConnectionString == null)
434 SetDefaultConnectionParameters (parameters);
436 SetProperties (parameters);
438 this.connectionString = connectionString;
441 private void SetDefaultConnectionParameters (NameValueCollection parameters)
443 if (null == parameters.Get ("APPLICATION NAME"))
444 parameters["APPLICATION NAME"] = "Mono SybaseClient Data Provider";
445 if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
446 parameters["CONNECT TIMEOUT"] = "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";
457 if (null == parameters.Get ("MIN POOL SIZE"))
458 parameters["MIN POOL SIZE"] = "0";
459 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
460 parameters["NETWORK LIBRARY"] = "dbmssocn";
461 if (null == parameters.Get ("PACKET SIZE"))
462 parameters["PACKET SIZE"] = "512";
463 if (null == parameters.Get ("PERSIST SECURITY INFO"))
464 parameters["PERSIST SECURITY INFO"] = "false";
465 if (null == parameters.Get ("POOLING"))
466 parameters["POOLING"] = "true";
467 if (null == parameters.Get ("WORKSTATION ID"))
468 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
471 private void SetProperties (NameValueCollection parameters)
473 string value;
474 foreach (string name in parameters) {
475 value = parameters [name];
477 switch (name) {
478 case "APPLICATION NAME" :
479 parms.ApplicationName = value;
480 break;
481 case "ATTACHDBFILENAME" :
482 case "EXTENDED PROPERTIES" :
483 case "INITIAL FILE NAME" :
484 break;
485 case "CONNECT TIMEOUT" :
486 case "CONNECTION TIMEOUT" :
487 connectionTimeout = Int32.Parse (value);
488 break;
489 case "CONNECTION LIFETIME" :
490 break;
491 case "CONNECTION RESET" :
492 connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
493 break;
494 case "CURRENT LANGUAGE" :
495 parms.Language = value;
496 break;
497 case "DATA SOURCE" :
498 case "SERVER" :
499 case "ADDRESS" :
500 case "ADDR" :
501 case "NETWORK ADDRESS" :
502 dataSource = value;
503 break;
504 case "ENLIST" :
505 break;
506 case "INITIAL CATALOG" :
507 case "DATABASE" :
508 parms.Database = value;
509 break;
510 case "INTEGRATED SECURITY" :
511 case "TRUSTED_CONNECTION" :
512 break;
513 case "MAX POOL SIZE" :
514 maxPoolSize = Int32.Parse (value);
515 break;
516 case "MIN POOL SIZE" :
517 minPoolSize = Int32.Parse (value);
518 break;
519 case "NET" :
520 case "NETWORK LIBRARY" :
521 if (!value.ToUpper ().Equals ("DBMSSOCN"))
522 throw new ArgumentException ("Unsupported network library.");
523 break;
524 case "PACKET SIZE" :
525 packetSize = Int32.Parse (value);
526 break;
527 case "PASSWORD" :
528 case "PWD" :
529 parms.Password = value;
530 break;
531 case "PERSIST SECURITY INFO" :
532 break;
533 case "POOLING" :
534 pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
535 break;
536 case "USER ID" :
537 parms.User = value;
538 break;
539 case "WORKSTATION ID" :
540 parms.Hostname = value;
541 break;
546 private static bool IsValidDatabaseName (string database)
548 if (database.Length > 32 || database.Length < 1)
549 return false;
551 if (database[0] == '"' && database[database.Length] == '"')
552 database = database.Substring (1, database.Length - 2);
553 else if (Char.IsDigit (database[0]))
554 return false;
556 if (database[0] == '_')
557 return false;
559 foreach (char c in database.Substring (1, database.Length - 1))
560 if (!Char.IsLetterOrDigit (c) && c != '_')
561 return false;
562 return true;
565 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
567 if (InfoMessage != null)
568 InfoMessage (this, value);
571 private void OnStateChange (StateChangeEventArgs value)
573 if (StateChange != null)
574 StateChange (this, value);
577 #endregion // Methods