2003-02-16 Daniel Morgan <danmorg@sc.rr.com>
[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 Hashtable SybaseConnectionPools = new Hashtable ();
32
33                 // The current connection pool
34                 SybaseConnectionPool 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 = 1533;
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)
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                                         pool = (SybaseConnectionPool) SybaseConnectionPools [connectionString];
299                                         if (pool == null) {
300                                                 ParseDataSource (dataSource, out port, out serverName);
301                                                 pool = new SybaseConnectionPool (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
302                                                 SybaseConnectionPools [connectionString] = pool;
303                                         }
304                                         tds = pool.AllocateConnection ();
305                                 }
306                         }
307                         catch (TdsTimeoutException e) {
308                                 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
309                         }
310
311                         tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
312                         tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
313
314                         if (!tds.IsConnected) {
315                                 tds.Connect (parms);
316                                 ChangeState (ConnectionState.Open);
317                                 ChangeDatabase (parms.Database);
318                         }
319                         else if (connectionReset) {
320                                 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
321                                 ChangeState (ConnectionState.Open);
322                         }
323                 }
324
325                 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName) 
326                 {
327                         theServerName = "";
328                         thePort = 1433; // default TCP port for SQL Server
329                                                 \r
330                         int idx = 0;\r
331                         if ((idx = theDataSource.IndexOf (",")) > -1) {\r
332                                 theServerName = theDataSource.Substring (0, idx);
333                                 string p = theDataSource.Substring (idx + 1);
334                                 thePort = Int32.Parse (p);
335                         }
336                         else {
337                                 theServerName = theDataSource;
338                         }
339                 }
340
341                 void SetConnectionString (string connectionString)
342                 {
343                         connectionString += ";";
344                         NameValueCollection parameters = new NameValueCollection ();
345
346                         if (connectionString == String.Empty)
347                                 return;
348
349                         bool inQuote = false;
350                         bool inDQuote = false;
351
352                         string name = String.Empty;
353                         string value = String.Empty;
354                         StringBuilder sb = new StringBuilder ();
355
356                         foreach (char c in connectionString)
357                         {
358                                 switch (c) {
359                                 case '\'':
360                                         inQuote = !inQuote;
361                                         break;
362                                 case '"' :
363                                         inDQuote = !inDQuote;
364                                         break;
365                                 case ';' :
366                                         if (!inDQuote && !inQuote) {
367                                                 if (name != String.Empty && name != null) {
368                                                         value = sb.ToString ();
369                                                         parameters [name.ToUpper ().Trim ()] = value.Trim ();
370                                                 }
371                                                 name = String.Empty;
372                                                 value = String.Empty;
373                                                 sb = new StringBuilder ();
374                                         }
375                                         else
376                                                 sb.Append (c);
377                                         break;
378                                 case '=' :
379                                         if (!inDQuote && !inQuote) {
380                                                 name = sb.ToString ();
381                                                 sb = new StringBuilder ();
382                                         }
383                                         else
384                                                 sb.Append (c);
385                                         break;
386                                 default:
387                                         sb.Append (c);
388                                         break;
389                                 }
390                         }
391
392                         if (this.ConnectionString == null)
393                         {
394                                 SetDefaultConnectionParameters (parameters);
395                         }
396
397                         SetProperties (parameters);
398
399                         this.connectionString = connectionString;
400                 }
401
402                 void SetDefaultConnectionParameters (NameValueCollection parameters)
403                 {
404                         if (null == parameters.Get ("APPLICATION NAME"))
405                                 parameters["APPLICATION NAME"] = ".Net SybaseClient Data Provider";
406                         if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
407                                 parameters["CONNECT TIMEOUT"] = "15";
408                         if (null == parameters.Get ("CONNECTION LIFETIME"))
409                                 parameters["CONNECTION LIFETIME"] = "0";
410                         if (null == parameters.Get ("CONNECTION RESET"))
411                                 parameters["CONNECTION RESET"] = "true";
412                         if (null == parameters.Get ("ENLIST"))
413                                 parameters["ENLIST"] = "true";
414                         if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
415                                 parameters["INTEGRATED SECURITY"] = "false";
416                         if (null == parameters.Get ("MAX POOL SIZE"))
417                                 parameters["MAX POOL SIZE"] = "100";
418                         if (null == parameters.Get ("MIN POOL SIZE"))
419                                 parameters["MIN POOL SIZE"] = "0";
420                         if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
421                                 parameters["NETWORK LIBRARY"] = "dbmssocn";
422                         if (null == parameters.Get ("PACKET SIZE"))
423                                 parameters["PACKET SIZE"] = "512";
424                         if (null == parameters.Get ("PERSIST SECURITY INFO"))
425                                 parameters["PERSIST SECURITY INFO"] = "false";
426                         if (null == parameters.Get ("POOLING"))
427                                 parameters["POOLING"] = "true";
428                         if (null == parameters.Get ("WORKSTATION ID"))
429                                 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
430                 }
431
432                 private void SetProperties (NameValueCollection parameters)
433                 {
434                         string value;
435                         foreach (string name in parameters) {
436                                 value = parameters[name];
437
438                                 switch (name) {
439                                 case "APPLICATION NAME" :
440                                         parms.ApplicationName = value;
441                                         break;
442                                 case "ATTACHDBFILENAME" :
443                                 case "EXTENDED PROPERTIES" :
444                                 case "INITIAL FILE NAME" :
445                                         break;
446                                 case "CONNECT TIMEOUT" :
447                                 case "CONNECTION TIMEOUT" :
448                                         connectionTimeout = Int32.Parse (value);
449                                         break;
450                                 case "CONNECTION LIFETIME" :
451                                         break;
452                                 case "CONNECTION RESET" :
453                                         connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
454                                         break;
455                                 case "CURRENT LANGUAGE" :
456                                         parms.Language = value;
457                                         break;
458                                 case "DATA SOURCE" :
459                                 case "SERVER" :
460                                 case "ADDRESS" :
461                                 case "ADDR" :
462                                 case "NETWORK ADDRESS" :
463                                         dataSource = value;
464                                         break;
465                                 case "ENLIST" :
466                                         break;
467                                 case "INITIAL CATALOG" :
468                                 case "DATABASE" :
469                                         parms.Database = value;
470                                         break;
471                                 case "INTEGRATED SECURITY" :
472                                 case "TRUSTED_CONNECTION" :
473                                         break;
474                                 case "MAX POOL SIZE" :
475                                         maxPoolSize = Int32.Parse (value);
476                                         break;
477                                 case "MIN POOL SIZE" :
478                                         minPoolSize = Int32.Parse (value);
479                                         break;
480                                 case "NET" :
481                                 case "NETWORK LIBRARY" :
482                                         if (!value.ToUpper ().Equals ("DBMSSOCN"))
483                                                 throw new ArgumentException ("Unsupported network library.");
484                                         break;
485                                 case "PACKET SIZE" :
486                                         packetSize = Int32.Parse (value);
487                                         break;
488                                 case "PASSWORD" :
489                                 case "PWD" :
490                                         parms.Password = value;
491                                         break;
492                                 case "PERSIST SECURITY INFO" :
493                                         break;
494                                 case "POOLING" :
495                                         pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
496                                         break;
497                                 case "USER ID" :
498                                         parms.User = value;
499                                         break;
500                                 case "WORKSTATION ID" :
501                                         parms.Hostname = value;
502                                         break;
503                                 }
504                         }
505                 }
506
507
508                 static bool IsValidDatabaseName (string database)
509                 {
510                         if (database.Length > 32 || database.Length < 1)
511                                 return false;
512
513                         if (database[0] == '"' && database[database.Length] == '"')
514                                 database = database.Substring (1, database.Length - 2);
515                         else if (Char.IsDigit (database[0]))
516                                 return false;
517
518                         if (database[0] == '_')
519                                 return false;
520
521                         foreach (char c in database.Substring (1, database.Length - 1))
522                                 if (!Char.IsLetterOrDigit (c) && c != '_')
523                                         return false;
524                         return true;
525                 }
526
527                 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
528                 {
529                         if (InfoMessage != null)
530                                 InfoMessage (this, value);
531                 }
532
533                 private void OnStateChange (StateChangeEventArgs value)
534                 {
535                         if (StateChange != null)
536                                 StateChange (this, value);
537                 }
538
539                 #endregion // Methods
540         }
541 }