New test.
[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 //
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.
31 //
32
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;
44
45 namespace Mono.Data.SybaseClient {
46         public sealed class SybaseConnection : Component, IDbConnection, ICloneable     
47         {
48                 #region Fields
49                 bool disposed = false;
50
51                 // The set of SQL connection pools
52                 static TdsConnectionPoolManager sybaseConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds50);
53
54                 // The current connection pool
55                 TdsConnectionPool pool;
56
57                 // The connection string that identifies this connection
58                 string connectionString = null;
59
60                 // The transaction object for the current transaction
61                 SybaseTransaction transaction = null;
62
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;
73
74                 // The current state
75                 ConnectionState state = ConnectionState.Closed;
76
77                 SybaseDataReader dataReader = null;
78
79                 // The TDS object
80                 ITds tds;
81
82                 #endregion // Fields
83
84                 #region Constructors
85
86                 public SybaseConnection () 
87                         : this (String.Empty)
88                 {
89                 }
90         
91                 public SybaseConnection (string connectionString) 
92                 {
93                         ConnectionString = connectionString;
94                 }
95
96                 #endregion // Constructors
97
98                 #region Properties
99                 
100                 public string ConnectionString  {
101                         get { return connectionString; }
102                         set { SetConnectionString (value); }
103                 }
104                 
105                 public int ConnectionTimeout {
106                         get { return connectionTimeout; }
107                 }
108
109                 public string Database  {
110                         get { return tds.Database; }
111                 }
112
113                 internal SybaseDataReader DataReader {
114                         get { return dataReader; }
115                         set { dataReader = value; }
116                 }
117
118                 public string DataSource {
119                         get { return dataSource; }
120                 }
121
122                 public int PacketSize {
123                         get { return packetSize; }
124                 }
125
126                 public string ServerVersion {
127                         get { return tds.ServerVersion; }
128                 }
129
130                 public ConnectionState State {
131                         get { return state; }
132                 }
133
134                 internal ITds Tds {
135                         get { return tds; }
136                 }
137
138                 internal SybaseTransaction Transaction {
139                         get { return transaction; }
140                         set { transaction = value; }
141                 }
142
143                 public string WorkstationId {
144                         get { return parms.Hostname; }
145                 }
146
147                 #endregion // Properties
148
149                 #region Events and Delegates
150                 
151                 public event SybaseInfoMessageEventHandler InfoMessage;
152                 public event StateChangeEventHandler StateChange;
153                 
154                 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
155                 {
156                         throw new SybaseException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SybaseClient Data Provider", e.State);
157                 }
158
159                 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
160                 {
161                         OnSybaseInfoMessage (CreateSybaseInfoMessageEvent (e.Errors));
162                 }
163
164                 #endregion // Events and Delegates
165
166                 #region Methods
167
168                 public SybaseTransaction BeginTransaction ()
169                 {
170                         return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
171                 }
172
173                 public SybaseTransaction BeginTransaction (IsolationLevel iso)
174                 {
175                         return BeginTransaction (iso, String.Empty);
176                 }
177
178                 public SybaseTransaction BeginTransaction (string transactionName)
179                 {
180                         return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
181                 }
182
183                 public SybaseTransaction BeginTransaction (IsolationLevel iso, string transactionName)
184                 {
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.");
189
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;
207                         }
208
209                         tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
210                         transaction = new SybaseTransaction (this, iso);
211                         return transaction;
212                 }
213
214                 public void ChangeDatabase (string database) 
215                 {
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));
221                 }
222
223                 private void ChangeState (ConnectionState currentState)
224                 {
225                         ConnectionState originalState = state;
226                         state = currentState;
227                         OnStateChange (CreateStateChangeEvent (originalState, currentState));
228                 }
229
230                 public void Close () 
231                 {
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);
241                 }
242
243                 public SybaseCommand CreateCommand () 
244                 {
245                         SybaseCommand command = new SybaseCommand ();
246                         command.Connection = this;
247                         return command;
248                 }
249
250                 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
251                 {
252                         return new StateChangeEventArgs (originalState, currentState);
253                 }
254
255                 private SybaseInfoMessageEventArgs CreateSybaseInfoMessageEvent (TdsInternalErrorCollection errors)
256                 {
257                         return new SybaseInfoMessageEventArgs (errors);
258                 }
259
260                 protected override void Dispose (bool disposing)
261                 {
262                         if (!disposed) {
263                                 if (disposing) {
264                                         if (State == ConnectionState.Open)
265                                                 Close ();
266                                         parms = null;
267                                         dataSource = null;
268                                 }
269                                 base.Dispose (disposing);
270                                 disposed = true;
271                         }
272                 }
273
274                 [MonoTODO]
275                 public void EnlistDistributedTransaction (ITransaction transaction)
276                 {
277                         throw new NotImplementedException ();
278                 }
279
280                 object ICloneable.Clone ()
281                 {
282                         return new SybaseConnection (ConnectionString);
283                 }
284
285                 IDbTransaction IDbConnection.BeginTransaction ()
286                 {
287                         return BeginTransaction ();
288                 }
289
290                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
291                 {
292                         return BeginTransaction (iso);
293                 }
294
295                 IDbCommand IDbConnection.CreateCommand ()
296                 {
297                         return CreateCommand ();
298                 }
299
300                 void IDisposable.Dispose ()
301                 {
302                         Dispose (true);
303                         GC.SuppressFinalize (this);
304                 }
305
306                 [MonoTODO ("Figure out the Sybase way to reset the connection.")]
307                 public void Open () 
308                 {
309                         string serverName = "";
310                         if (connectionString == null || connectionString.Equals (""))
311                                 throw new InvalidOperationException ("Connection string has not been initialized.");
312
313                         try {
314                                 if (!pooling) {
315                                         ParseDataSource (dataSource, out port, out serverName);
316                                         tds = new Tds50 (serverName, port, PacketSize, ConnectionTimeout);
317                                 }
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 ();
323                                 }
324                         }
325                         catch (TdsTimeoutException e) {
326                                 throw SybaseException.FromTdsInternalException ((TdsInternalException) e);
327                         }
328
329                         tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
330                         tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
331
332                         if (!tds.IsConnected) {
333                                 try {
334                                         tds.Connect (parms);
335                                         ChangeState (ConnectionState.Open);
336                                         ChangeDatabase (parms.Database);
337                                 }
338                                 catch {
339                                         if (pooling)
340                                                 pool.ReleaseConnection (tds);
341                                         throw;
342                                 }
343                         }
344                         else if (connectionReset) {
345                                 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
346                                 ChangeState (ConnectionState.Open);
347                         }
348                 }
349
350                 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName) 
351                 {
352                         theServerName = "";
353                         thePort = 2048; 
354                                                 
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);
360                         }
361                         else {
362                                 theServerName = theDataSource;
363                         }
364                 }
365
366                 private string ParseValue (string name, string value)
367                 {
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;
373                 }
374
375                 private void SetConnectionString (string connectionString)
376                 {
377                         if (connectionString == String.Empty) {
378                                 this.connectionString = connectionString;
379                                 return;
380                         }
381
382                         NameValueCollection parameters = new NameValueCollection ();
383
384                         string name = String.Empty;
385                         string value = String.Empty;
386                         StringBuilder sb = new StringBuilder ();
387
388                         char delimiter = '\0';
389
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 ();
408                                         } 
409                                         else
410                                                 sb.Append (c);
411                                         break;
412                                 case '=' :
413                                         if (delimiter.Equals ('\0')) {
414                                                 name = sb.ToString ();
415                                                 sb = new StringBuilder ();
416                                         }
417                                         else
418                                                 sb.Append (c);
419                                         break;
420                                 default:
421                                         sb.Append (c);
422                                         break;
423                                 }
424                         }
425
426                         if (!delimiter.Equals ('\0'))
427                                 throw new ArgumentException (String.Format ("Matching end delimiter {0} not found in connection option value.", delimiter));
428
429                         value = ParseValue (name, sb.ToString ());
430                         if (!value.Equals (""))
431                                 parameters [name.ToUpper ().Trim ()] = value;
432
433                         SetDefaultConnectionParameters (parameters);
434                         SetProperties (parameters);
435
436                         this.connectionString = connectionString;
437                 }
438
439                 private void SetDefaultConnectionParameters (NameValueCollection parameters)
440                 {
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;
446                         }
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                                 maxPoolSize = 100;
458                         }
459                         if (null == parameters.Get ("MIN POOL SIZE")) {
460                                 parameters["MIN POOL SIZE"] = "0";
461                                 maxPoolSize = 0;
462                         }
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";
467                                 packetSize = 512;
468                         }
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;
475                 }
476
477                 private void SetProperties (NameValueCollection parameters)
478                 {
479                         string value;
480                         foreach (string name in parameters) {
481                                 value = parameters [name];
482
483                                 switch (name) {
484                                 case "APPLICATION NAME" :
485                                         parms.ApplicationName = value;
486                                         break;
487                                 case "ATTACHDBFILENAME" :
488                                 case "EXTENDED PROPERTIES" :
489                                 case "INITIAL FILE NAME" :
490                                         break;
491                                 case "CONNECT TIMEOUT" :
492                                 case "CONNECTION TIMEOUT" :
493                                         connectionTimeout = Int32.Parse (value);
494                                         break;
495                                 case "CONNECTION LIFETIME" :
496                                         break;
497                                 case "CONNECTION RESET" :
498                                         connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
499                                         break;
500                                 case "CURRENT LANGUAGE" :
501                                         parms.Language = value;
502                                         break;
503                                 case "DATA SOURCE" :
504                                 case "SERVER" :
505                                 case "ADDRESS" :
506                                 case "ADDR" :
507                                 case "NETWORK ADDRESS" :
508                                         dataSource = value;
509                                         break;
510                                 case "ENLIST" :
511                                         break;
512                                 case "INITIAL CATALOG" :
513                                 case "DATABASE" :
514                                         parms.Database = value;
515                                         break;
516                                 case "INTEGRATED SECURITY" :
517                                 case "TRUSTED_CONNECTION" :
518                                         break;
519                                 case "MAX POOL SIZE" :
520                                         maxPoolSize = Int32.Parse (value);
521                                         break;
522                                 case "MIN POOL SIZE" :
523                                         minPoolSize = Int32.Parse (value);
524                                         break;
525                                 case "NET" :
526                                 case "NETWORK LIBRARY" :
527                                         if (!value.ToUpper ().Equals ("DBMSSOCN"))
528                                                 throw new ArgumentException ("Unsupported network library.");
529                                         break;
530                                 case "PACKET SIZE" :
531                                         packetSize = Int32.Parse (value);
532                                         break;
533                                 case "PASSWORD" :
534                                 case "PWD" :
535                                         parms.Password = value;
536                                         break;
537                                 case "PERSIST SECURITY INFO" :
538                                         break;
539                                 case "POOLING" :
540                                         pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
541                                         break;
542                                 case "USER ID" :
543                                         parms.User = value;
544                                         break;
545                                 case "WORKSTATION ID" :
546                                         parms.Hostname = value;
547                                         break;
548                                 }
549                         }
550                 }
551
552                 private static bool IsValidDatabaseName (string database)
553                 {
554                         if (database.Length > 32 || database.Length < 1)
555                                 return false;
556
557                         if (database[0] == '"' && database[database.Length] == '"')
558                                 database = database.Substring (1, database.Length - 2);
559                         else if (Char.IsDigit (database[0]))
560                                 return false;
561
562                         if (database[0] == '_')
563                                 return false;
564
565                         foreach (char c in database.Substring (1, database.Length - 1))
566                                 if (!Char.IsLetterOrDigit (c) && c != '_')
567                                         return false;
568                         return true;
569                 }
570
571                 private void OnSybaseInfoMessage (SybaseInfoMessageEventArgs value)
572                 {
573                         if (InfoMessage != null)
574                                 InfoMessage (this, value);
575                 }
576
577                 private void OnStateChange (StateChangeEventArgs value)
578                 {
579                         if (StateChange != null)
580                                 StateChange (this, value);
581                 }
582
583                 #endregion // Methods
584         }
585 }