* Mono.Data.SybaseClient.dll.sources: Removed SybaseConnectionPool.cs.
[mono.git] / mcs / class / Mono.Data.SybaseClient / Mono.Data.SybaseClient / SybaseConnection.cs
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
10 //
11
12 using Mono.Data.Tds.Protocol;
13 using System;
14 using System.Collections;
15 using System.Collections.Specialized;
16 using System.ComponentModel;
17 using System.Data;
18 using System.Data.Common;
19 using System.EnterpriseServices;
20 using System.Net;
21 using System.Net.Sockets;
22 using System.Text;
23
24 namespace Mono.Data.SybaseClient {
25         public sealed class SybaseConnection : Component, IDbConnection, ICloneable     
26         {
27                 #region Fields
28                 bool disposed = false;
29
30                 // The set of SQL connection pools
31                 static TdsConnectionPoolManager sybaseConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds50);
32
33                 // The current connection pool
34                 TdsConnectionPool pool;
35
36                 // The connection string that identifies this connection
37                 string connectionString = null;
38
39                 // The transaction object for the current transaction
40                 SybaseTransaction transaction = null;
41
42                 // Connection parameters
43                 TdsConnectionParameters parms = new TdsConnectionParameters ();
44                 bool connectionReset;
45                 bool pooling;
46                 string dataSource;
47                 int connectionTimeout;
48                 int minPoolSize;
49                 int maxPoolSize;
50                 int packetSize;
51                 int port = 2048;
52
53                 // The current state
54                 ConnectionState state = ConnectionState.Closed;
55
56                 SybaseDataReader dataReader = null;
57
58                 // The TDS object
59                 ITds tds;
60
61                 #endregion // Fields
62
63                 #region Constructors
64
65                 public SybaseConnection () 
66                         : this (String.Empty)
67                 {
68                 }
69         
70                 public SybaseConnection (string connectionString) 
71                 {
72                         ConnectionString = connectionString;
73                 }
74
75                 #endregion // Constructors
76
77                 #region Properties
78                 
79                 public string ConnectionString  {
80                         get { return connectionString; }
81                         set { SetConnectionString (value); }
82                 }
83                 
84                 public int ConnectionTimeout {
85                         get { return connectionTimeout; }
86                 }
87
88                 public string Database  {
89                         get { return tds.Database; }
90                 }
91
92                 internal SybaseDataReader DataReader {
93                         get { return dataReader; }
94                         set { dataReader = value; }
95                 }
96
97                 public string DataSource {
98                         get { return dataSource; }
99                 }
100
101                 public int PacketSize {
102                         get { return packetSize; }
103                 }
104
105                 public string ServerVersion {
106                         get { return tds.ServerVersion; }
107                 }
108
109                 public ConnectionState State {
110                         get { return state; }
111                 }
112
113                 internal ITds Tds {
114                         get { return tds; }
115                 }
116
117                 internal SybaseTransaction Transaction {
118                         get { return transaction; }
119                         set { transaction = value; }
120                 }
121
122                 public string WorkstationId {
123                         get { return parms.Hostname; }
124                 }
125
126                 #endregion // Properties
127
128                 #region Events and Delegates
129                 
130                 public event SybaseInfoMessageEventHandler InfoMessage;
131                 public event StateChangeEventHandler StateChange;
132                 
133                 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
134                 {
135                         throw new SybaseException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SybaseClient Data Provider", e.State);
136                 }
137
138                 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
139                 {
140                         OnSybaseInfoMessage (CreateSybaseInfoMessageEvent (e.Errors));
141                 }
142
143                 #endregion // Events and Delegates
144
145                 #region Methods
146
147                 public SybaseTransaction BeginTransaction ()
148                 {
149                         return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
150                 }
151
152                 public SybaseTransaction BeginTransaction (IsolationLevel iso)
153                 {
154                         return BeginTransaction (iso, String.Empty);
155                 }
156
157                 public SybaseTransaction BeginTransaction (string transactionName)
158                 {
159                         return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
160                 }
161
162                 public SybaseTransaction BeginTransaction (IsolationLevel iso, string transactionName)
163                 {
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.");
168
169                         string isolevel = String.Empty;
170                         switch (iso) {
171                         case IsolationLevel.Chaos:
172                                 isolevel = "CHAOS";
173                                 break;
174                         case IsolationLevel.ReadCommitted:
175                                 isolevel = "READ COMMITTED";
176                                 break;
177                         case IsolationLevel.ReadUncommitted:
178                                 isolevel = "READ UNCOMMITTED";
179                                 break;
180                         case IsolationLevel.RepeatableRead:
181                                 isolevel = "REPEATABLE READ";
182                                 break;
183                         case IsolationLevel.Serializable:
184                                 isolevel = "SERIALIZABLE";
185                                 break;
186                         }
187
188                         tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
189                         transaction = new SybaseTransaction (this, iso);
190                         return transaction;
191                 }
192
193                 public void ChangeDatabase (string database) 
194                 {
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));
200                 }
201
202                 private void ChangeState (ConnectionState currentState)
203                 {
204                         ConnectionState originalState = state;
205                         state = currentState;
206                         OnStateChange (CreateStateChangeEvent (originalState, currentState));
207                 }
208
209                 public void Close () 
210                 {
211                         if (Transaction != null && Transaction.IsOpen)
212                                 Transaction.Rollback ();
213                         if (pooling)
214                                 pool.ReleaseConnection (tds);
215                         else
216                                 tds.Disconnect ();
217                         tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
218                         tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
219                         ChangeState (ConnectionState.Closed);
220                 }
221
222                 public SybaseCommand CreateCommand () 
223                 {
224                         SybaseCommand command = new SybaseCommand ();
225                         command.Connection = this;
226                         return command;
227                 }
228
229                 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
230                 {
231                         return new StateChangeEventArgs (originalState, currentState);
232                 }
233
234                 private SybaseInfoMessageEventArgs CreateSybaseInfoMessageEvent (TdsInternalErrorCollection errors)
235                 {
236                         return new SybaseInfoMessageEventArgs (errors);
237                 }
238
239                 protected override void Dispose (bool disposing)
240                 {
241                         if (!disposed) {
242                                 if (disposing) {
243                                         if (State == ConnectionState.Open)
244                                                 Close ();
245                                         parms = null;
246                                         dataSource = null;
247                                 }
248                                 base.Dispose (disposing);
249                                 disposed = true;
250                         }
251                 }
252
253                 [MonoTODO]
254                 public void EnlistDistributedTransaction (ITransaction transaction)
255                 {
256                         throw new NotImplementedException ();
257                 }
258
259                 object ICloneable.Clone ()
260                 {
261                         return new SybaseConnection (ConnectionString);
262                 }
263
264                 IDbTransaction IDbConnection.BeginTransaction ()
265                 {
266                         return BeginTransaction ();
267                 }
268
269                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
270                 {
271                         return BeginTransaction (iso);
272                 }
273
274                 IDbCommand IDbConnection.CreateCommand ()
275                 {
276                         return CreateCommand ();
277                 }
278
279                 void IDisposable.Dispose ()
280                 {
281                         Dispose (true);
282                         GC.SuppressFinalize (this);
283                 }
284
285                 [MonoTODO ("Figure out the Sybase way to reset the connection.")]
286                 public void Open () 
287                 {
288                         string serverName = "";
289                         if (connectionString == null || connectionString.Equals (""))
290                                 throw new InvalidOperationException ("Connection string has not been initialized.");
291
292                         try {
293                                 if (!pooling) {
294                                         ParseDataSource (dataSource, out port, out serverName);
295                                         tds = new Tds50 (serverName, port, PacketSize, ConnectionTimeout);
296                                 }
297                                 else {
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 ();
302                                 }
303                         }
304                         catch (TdsTimeoutException e) {
305                                 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
306                         }
307
308                         tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
309                         tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
310
311                         if (!tds.IsConnected) {
312                                 try {
313                                         tds.Connect (parms);
314                                         ChangeState (ConnectionState.Open);
315                                         ChangeDatabase (parms.Database);
316                                 }
317                                 catch {
318                                         if (pooling)
319                                                 pool.ReleaseConnection (tds);
320                                         throw;
321                                 }
322                         }
323                         else if (connectionReset) {
324                                 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
325                                 ChangeState (ConnectionState.Open);
326                         }
327                 }
328
329                 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName) 
330                 {
331                         theServerName = "";
332                         thePort = 2048; 
333                                                 
334                         int idx = 0;
335                         if ((idx = theDataSource.IndexOf (",")) > -1) {
336                                 theServerName = theDataSource.Substring (0, idx);
337                                 string p = theDataSource.Substring (idx + 1);
338                                 thePort = Int32.Parse (p);
339                         }
340                         else {
341                                 theServerName = theDataSource;
342                         }
343                 }
344
345                 private string ParseValue (string name, string value)
346                 {
347                         if (name.Length == 0 && value.Length > 0)
348                                 throw new ArgumentException ("Expected '=' delimiter while parsing connection value pair.");
349                         if (name.Length > 0)
350                                 return value.Trim ();
351                         return String.Empty;
352                 }
353
354                 private void SetConnectionString (string connectionString)
355                 {
356                         if (connectionString == String.Empty) {
357                                 this.connectionString = connectionString;
358                                 return;
359                         }
360
361                         NameValueCollection parameters = new NameValueCollection ();
362
363                         string name = String.Empty;
364                         string value = String.Empty;
365                         StringBuilder sb = new StringBuilder ();
366
367                         char delimiter = '\0';
368
369                         foreach (char c in connectionString) {
370                                 switch (c) {
371                                 case '\'' :
372                                 case '"' :
373                                         if (delimiter.Equals (c))
374                                                 delimiter = '\0';
375                                         else if (delimiter.Equals ('\0'))
376                                                 delimiter = c;
377                                         else
378                                                 sb.Append (c);
379                                         break;
380                                 case ';' :
381                                         if (delimiter.Equals ('\0')) {
382                                                 value = ParseValue (name, sb.ToString ());
383                                                 if (!value.Equals ("")) 
384                                                         parameters [name.ToUpper ().Trim ()] = value;
385                                                 name = String.Empty;
386                                                 sb = new StringBuilder ();
387                                         } 
388                                         else
389                                                 sb.Append (c);
390                                         break;
391                                 case '=' :
392                                         if (delimiter.Equals ('\0')) {
393                                                 name = sb.ToString ();
394                                                 sb = new StringBuilder ();
395                                         }
396                                         else
397                                                 sb.Append (c);
398                                         break;
399                                 default:
400                                         sb.Append (c);
401                                         break;
402                                 }
403                         }
404
405                         if (!delimiter.Equals ('\0'))
406                                 throw new ArgumentException (String.Format ("Matching end delimiter {0} not found in connection option value.", delimiter));
407
408                         value = ParseValue (name, sb.ToString ());
409                         if (!value.Equals (""))
410                                 parameters [name.ToUpper ().Trim ()] = value;
411
412                         if (this.ConnectionString == null)
413                                 SetDefaultConnectionParameters (parameters);
414
415                         SetProperties (parameters);
416
417                         this.connectionString = connectionString;
418                 }
419
420                 private void SetDefaultConnectionParameters (NameValueCollection parameters)
421                 {
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;
448                 }
449
450                 private void SetProperties (NameValueCollection parameters)
451                 {
452                         string value;
453                         foreach (string name in parameters) {
454                                 value = parameters [name];
455
456                                 switch (name) {
457                                 case "APPLICATION NAME" :
458                                         parms.ApplicationName = value;
459                                         break;
460                                 case "ATTACHDBFILENAME" :
461                                 case "EXTENDED PROPERTIES" :
462                                 case "INITIAL FILE NAME" :
463                                         break;
464                                 case "CONNECT TIMEOUT" :
465                                 case "CONNECTION TIMEOUT" :
466                                         connectionTimeout = Int32.Parse (value);
467                                         break;
468                                 case "CONNECTION LIFETIME" :
469                                         break;
470                                 case "CONNECTION RESET" :
471                                         connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
472                                         break;
473                                 case "CURRENT LANGUAGE" :
474                                         parms.Language = value;
475                                         break;
476                                 case "DATA SOURCE" :
477                                 case "SERVER" :
478                                 case "ADDRESS" :
479                                 case "ADDR" :
480                                 case "NETWORK ADDRESS" :
481                                         dataSource = value;
482                                         break;
483                                 case "ENLIST" :
484                                         break;
485                                 case "INITIAL CATALOG" :
486                                 case "DATABASE" :
487                                         parms.Database = value;
488                                         break;
489                                 case "INTEGRATED SECURITY" :
490                                 case "TRUSTED_CONNECTION" :
491                                         break;
492                                 case "MAX POOL SIZE" :
493                                         maxPoolSize = Int32.Parse (value);
494                                         break;
495                                 case "MIN POOL SIZE" :
496                                         minPoolSize = Int32.Parse (value);
497                                         break;
498                                 case "NET" :
499                                 case "NETWORK LIBRARY" :
500                                         if (!value.ToUpper ().Equals ("DBMSSOCN"))
501                                                 throw new ArgumentException ("Unsupported network library.");
502                                         break;
503                                 case "PACKET SIZE" :
504                                         packetSize = Int32.Parse (value);
505                                         break;
506                                 case "PASSWORD" :
507                                 case "PWD" :
508                                         parms.Password = value;
509                                         break;
510                                 case "PERSIST SECURITY INFO" :
511                                         break;
512                                 case "POOLING" :
513                                         pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
514                                         break;
515                                 case "USER ID" :
516                                         parms.User = value;
517                                         break;
518                                 case "WORKSTATION ID" :
519                                         parms.Hostname = value;
520                                         break;
521                                 }
522                         }
523                 }
524
525                 private static bool IsValidDatabaseName (string database)
526                 {
527                         if (database.Length > 32 || database.Length < 1)
528                                 return false;
529
530                         if (database[0] == '"' && database[database.Length] == '"')
531                                 database = database.Substring (1, database.Length - 2);
532                         else if (Char.IsDigit (database[0]))
533                                 return false;
534
535                         if (database[0] == '_')
536                                 return false;
537
538                         foreach (char c in database.Substring (1, database.Length - 1))
539                                 if (!Char.IsLetterOrDigit (c) && c != '_')
540                                         return false;
541                         return true;
542                 }
543
544                 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
545                 {
546                         if (InfoMessage != null)
547                                 InfoMessage (this, value);
548                 }
549
550                 private void OnStateChange (StateChangeEventArgs value)
551                 {
552                         if (StateChange != null)
553                                 StateChange (this, value);
554                 }
555
556                 #endregion // Methods
557         }
558 }