New test.
[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 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.TdsClient {
46         public sealed class TdsConnection : Component, IDbConnection, ICloneable        
47         {
48                 #region Fields
49                 bool disposed = false;
50
51                 // The set of SQL connection pools
52                 static TdsConnectionPoolManager tdsConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds42);
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                 TdsTransaction 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 = 1533;
73
74                 // The current state
75                 ConnectionState state = ConnectionState.Closed;
76
77                 TdsDataReader dataReader = null;
78
79                 // The TDS object
80                 ITds tds;
81
82                 #endregion // Fields
83
84                 #region Constructors
85
86                 public TdsConnection () 
87                         : this (String.Empty)
88                 {
89                 }
90         
91                 public TdsConnection (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 TdsDataReader 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 TdsTransaction 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 TdsInfoMessageEventHandler InfoMessage;
152                 public event StateChangeEventHandler StateChange;
153                 
154                 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
155                 {
156                         throw new TdsException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono TdsClient Data Provider", e.State);
157                 }
158
159                 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
160                 {
161                         OnTdsInfoMessage (CreateTdsInfoMessageEvent (e.Errors));
162                 }
163
164                 #endregion // Events and Delegates
165
166                 #region Methods
167
168                 public TdsTransaction BeginTransaction ()
169                 {
170                         return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
171                 }
172
173                 public TdsTransaction BeginTransaction (IsolationLevel iso)
174                 {
175                         return BeginTransaction (iso, String.Empty);
176                 }
177
178                 public TdsTransaction BeginTransaction (string transactionName)
179                 {
180                         return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
181                 }
182
183                 public TdsTransaction 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 ("TdsConnection 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 TdsTransaction (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 TdsCommand CreateCommand () 
244                 {
245                         TdsCommand command = new TdsCommand ();
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 TdsInfoMessageEventArgs CreateTdsInfoMessageEvent (TdsInternalErrorCollection errors)
256                 {
257                         return new TdsInfoMessageEventArgs (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 TdsConnection (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 Tds way to reset the connection.")]
307                 public void Open () 
308                 {
309                         string serverName = "";
310                         if (connectionString == null)
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 Tds42 (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 = tdsConnectionPools.GetConnectionPool (connectionString, info);
322                                         tds = pool.GetConnection ();
323                                 }
324                         }
325                         catch (TdsTimeoutException e) {
326                                 throw TdsException.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 = 1433; // default TCP port for SQL Server
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                 void SetConnectionString (string connectionString)
367                 {
368                         connectionString += ";";
369                         NameValueCollection parameters = new NameValueCollection ();
370
371                         if (connectionString == String.Empty)
372                                 return;
373
374                         bool inQuote = false;
375                         bool inDQuote = false;
376
377                         string name = String.Empty;
378                         string value = String.Empty;
379                         StringBuilder sb = new StringBuilder ();
380
381                         foreach (char c in connectionString)
382                         {
383                                 switch (c) {
384                                 case '\'':
385                                         inQuote = !inQuote;
386                                         break;
387                                 case '"' :
388                                         inDQuote = !inDQuote;
389                                         break;
390                                 case ';' :
391                                         if (!inDQuote && !inQuote) {
392                                                 if (name != String.Empty && name != null) {
393                                                         value = sb.ToString ();
394                                                         parameters [name.ToUpper ().Trim ()] = value.Trim ();
395                                                 }
396                                                 name = String.Empty;
397                                                 value = String.Empty;
398                                                 sb = new StringBuilder ();
399                                         }
400                                         else
401                                                 sb.Append (c);
402                                         break;
403                                 case '=' :
404                                         if (!inDQuote && !inQuote) {
405                                                 name = sb.ToString ();
406                                                 sb = new StringBuilder ();
407                                         }
408                                         else
409                                                 sb.Append (c);
410                                         break;
411                                 default:
412                                         sb.Append (c);
413                                         break;
414                                 }
415                         }
416
417                         if (this.ConnectionString == null)
418                         {
419                                 SetDefaultConnectionParameters (parameters);
420                         }
421
422                         SetProperties (parameters);
423
424                         this.connectionString = connectionString;
425                 }
426
427                 void SetDefaultConnectionParameters (NameValueCollection parameters)
428                 {
429                         if (null == parameters.Get ("APPLICATION NAME"))
430                                 parameters["APPLICATION NAME"] = ".Net TdsClient Data Provider";
431                         if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
432                                 parameters["CONNECT TIMEOUT"] = "15";
433                         if (null == parameters.Get ("CONNECTION LIFETIME"))
434                                 parameters["CONNECTION LIFETIME"] = "0";
435                         if (null == parameters.Get ("CONNECTION RESET"))
436                                 parameters["CONNECTION RESET"] = "true";
437                         if (null == parameters.Get ("ENLIST"))
438                                 parameters["ENLIST"] = "true";
439                         if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
440                                 parameters["INTEGRATED SECURITY"] = "false";
441                         if (null == parameters.Get ("MAX POOL SIZE"))
442                                 parameters["MAX POOL SIZE"] = "100";
443                         if (null == parameters.Get ("MIN POOL SIZE"))
444                                 parameters["MIN POOL SIZE"] = "0";
445                         if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
446                                 parameters["NETWORK LIBRARY"] = "dbmssocn";
447                         if (null == parameters.Get ("PACKET SIZE"))
448                                 parameters["PACKET SIZE"] = "512";
449                         if (null == parameters.Get ("PERSIST SECURITY INFO"))
450                                 parameters["PERSIST SECURITY INFO"] = "false";
451                         if (null == parameters.Get ("POOLING"))
452                                 parameters["POOLING"] = "true";
453                         if (null == parameters.Get ("WORKSTATION ID"))
454                                 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
455                 }
456
457                 private void SetProperties (NameValueCollection parameters)
458                 {
459                         string value;
460                         foreach (string name in parameters) {
461                                 value = parameters[name];
462
463                                 switch (name) {
464                                 case "APPLICATION NAME" :
465                                         parms.ApplicationName = value;
466                                         break;
467                                 case "ATTACHDBFILENAME" :
468                                 case "EXTENDED PROPERTIES" :
469                                 case "INITIAL FILE NAME" :
470                                         break;
471                                 case "CONNECT TIMEOUT" :
472                                 case "CONNECTION TIMEOUT" :
473                                         connectionTimeout = Int32.Parse (value);
474                                         break;
475                                 case "CONNECTION LIFETIME" :
476                                         break;
477                                 case "CONNECTION RESET" :
478                                         connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
479                                         break;
480                                 case "CURRENT LANGUAGE" :
481                                         parms.Language = value;
482                                         break;
483                                 case "DATA SOURCE" :
484                                 case "SERVER" :
485                                 case "ADDRESS" :
486                                 case "ADDR" :
487                                 case "NETWORK ADDRESS" :
488                                         dataSource = value;
489                                         break;
490                                 case "ENLIST" :
491                                         break;
492                                 case "INITIAL CATALOG" :
493                                 case "DATABASE" :
494                                         parms.Database = value;
495                                         break;
496                                 case "INTEGRATED SECURITY" :
497                                 case "TRUSTED_CONNECTION" :
498                                         break;
499                                 case "MAX POOL SIZE" :
500                                         maxPoolSize = Int32.Parse (value);
501                                         break;
502                                 case "MIN POOL SIZE" :
503                                         minPoolSize = Int32.Parse (value);
504                                         break;
505                                 case "NET" :
506                                 case "NETWORK LIBRARY" :
507                                         if (!value.ToUpper ().Equals ("DBMSSOCN"))
508                                                 throw new ArgumentException ("Unsupported network library.");
509                                         break;
510                                 case "PACKET SIZE" :
511                                         packetSize = Int32.Parse (value);
512                                         break;
513                                 case "PASSWORD" :
514                                 case "PWD" :
515                                         parms.Password = value;
516                                         break;
517                                 case "PERSIST SECURITY INFO" :
518                                         break;
519                                 case "POOLING" :
520                                         pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
521                                         break;
522                                 case "USER ID" :
523                                         parms.User = value;
524                                         break;
525                                 case "WORKSTATION ID" :
526                                         parms.Hostname = value;
527                                         break;
528                                 }
529                         }
530                 }
531
532
533                 static bool IsValidDatabaseName (string database)
534                 {
535                         if (database.Length > 32 || database.Length < 1)
536                                 return false;
537
538                         if (database[0] == '"' && database[database.Length] == '"')
539                                 database = database.Substring (1, database.Length - 2);
540                         else if (Char.IsDigit (database[0]))
541                                 return false;
542
543                         if (database[0] == '_')
544                                 return false;
545
546                         foreach (char c in database.Substring (1, database.Length - 1))
547                                 if (!Char.IsLetterOrDigit (c) && c != '_')
548                                         return false;
549                         return true;
550                 }
551
552                 private void OnTdsInfoMessage (TdsInfoMessageEventArgs value)
553                 {
554                         if (InfoMessage != null)
555                                 InfoMessage (this, value);
556                 }
557
558                 private void OnStateChange (StateChangeEventArgs value)
559                 {
560                         if (StateChange != null)
561                                 StateChange (this, value);
562                 }
563
564                 #endregion // Methods
565         }
566 }