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