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