In System.Data.ProviderBase:
[mono.git] / mcs / class / System.Data / System.Data.SqlClient / SqlConnection.cs
1 //
2 // System.Data.SqlClient.SqlConnection.cs
3 //
4 // Authors:
5 //   Rodrigo Moya (rodrigo@ximian.com)
6 //   Daniel Morgan (danmorg@sc.rr.com)
7 //   Tim Coleman (tim@timcoleman.com)
8 //   Phillip Jerkins (Phillip.Jerkins@morgankeegan.com)
9 //   Diego Caravana (diego@toth.it)
10 //
11 // Copyright (C) Ximian, Inc 2002
12 // Copyright (C) Daniel Morgan 2002, 2003
13 // Copyright (C) Tim Coleman, 2002, 2003
14 // Copyright (C) Phillip Jerkins, 2003
15 //
16
17 //
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 //
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
27 // 
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
30 // 
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 //
39
40 using Mono.Data.Tds;
41 using Mono.Data.Tds.Protocol;
42 using System;
43 using System.Collections;
44 using System.Collections.Specialized;
45 using System.ComponentModel;
46 using System.Data;
47 using System.Data.Common;
48
49 #if NET_2_0
50 using System.Data.ProviderBase;
51 #endif // NET_2_0
52
53 using System.EnterpriseServices;
54 using System.Globalization;
55 using System.Net;
56 using System.Net.Sockets;
57 using System.Text;
58 using System.Xml;
59
60 namespace System.Data.SqlClient {
61         [DefaultEvent ("InfoMessage")]
62 #if NET_2_0
63         public sealed class SqlConnection : DbConnectionBase, IDbConnection, ICloneable 
64 #else
65         public sealed class SqlConnection : Component, IDbConnection, ICloneable                        
66 #endif // NET_2_0
67         {
68                 #region Fields
69                 bool disposed = false;
70
71                 // The set of SQL connection pools
72                 static TdsConnectionPoolManager sqlConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds70);
73
74                 // The current connection pool
75                 TdsConnectionPool pool;
76
77                 // The connection string that identifies this connection
78                 string connectionString = null;
79
80                 // The transaction object for the current transaction
81                 SqlTransaction transaction = null;
82
83                 // Connection parameters
84                 TdsConnectionParameters parms = new TdsConnectionParameters ();
85                 NameValueCollection connStringParameters = null;
86                 bool connectionReset;
87                 bool pooling;
88                 string dataSource;
89                 int connectionTimeout;
90                 int minPoolSize;
91                 int maxPoolSize;
92                 int packetSize;
93                 int port = 1433;
94
95                 // The current state
96                 ConnectionState state = ConnectionState.Closed;
97
98                 SqlDataReader dataReader = null;
99                 XmlReader xmlReader = null;
100
101                 // The TDS object
102                 ITds tds;
103
104                 #endregion // Fields
105
106                 #region Constructors
107
108                 public SqlConnection () 
109                         : this (String.Empty)
110                 {
111                 }
112         
113                 public SqlConnection (string connectionString)
114                 {
115                         Init (connectionString);
116                 }
117
118 #if NET_2_0
119                 internal SqlConnection (DbConnectionFactory connectionFactory) : base (connectionFactory)
120                 {
121                         Init (String.Empty);
122                 }
123
124 #endif //NET_2_0
125
126                 private void Init (string connectionString)
127                 {
128                         connectionTimeout       = 15; // default timeout
129                         dataSource              = ""; // default datasource
130                         packetSize              = 8192; // default packetsize
131                         ConnectionString        = connectionString;
132                 }
133                 
134
135
136                 #endregion // Constructors
137
138                 #region Properties
139
140                 [DataCategory ("Data")]
141                 [DataSysDescription ("Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'.")]
142                 [DefaultValue ("")]
143                 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
144                 [RecommendedAsConfigurable (true)]      
145                 [RefreshProperties (RefreshProperties.All)]
146                 [MonoTODO("persist security info, encrypt, enlist and , attachdbfilename keyword not implemented")]
147                 public 
148 #if NET_2_0
149                 override
150 #endif // NET_2_0
151                 string ConnectionString {
152                         get { return connectionString; }
153                         set { SetConnectionString (value); }
154                 }
155         
156                 [DataSysDescription ("Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.")] 
157                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
158                 public 
159 #if NET_2_0
160                 override
161 #endif // NET_2_0
162                 int ConnectionTimeout {
163                         get { return connectionTimeout; }
164                 }
165
166                 [DataSysDescription ("Current SQL Server database, 'Initial Catalog=X' in the ConnectionString.")]
167                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
168                 public 
169 #if NET_2_0
170                 override
171 #endif // NET_2_0
172                 string Database {
173                         get { 
174                                 if (State == ConnectionState.Open)
175                                         return tds.Database; 
176                                 return GetConnStringKeyValue ("DATABASE", "INITIAL CATALOG");
177                         }
178                 }
179                 
180                 internal SqlDataReader DataReader {
181                         get { return dataReader; }
182                         set { dataReader = value; }
183                 }
184
185                 [DataSysDescription ("Current SqlServer that the connection is opened to, 'Data Source=X' in the ConnectionString.")]   
186                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
187                 public 
188 #if NET_2_0
189                 override
190 #endif // NET_2_0
191                 string DataSource {
192                         get { return dataSource; }
193                 }
194
195                 [DataSysDescription ("Network packet size, 'Packet Size=x' in the ConnectionString.")]  
196                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
197                 public int PacketSize {
198                         get { return packetSize; }
199                 }
200
201                 [Browsable (false)]
202                 [DataSysDescription ("Version of the SQL Server accessed by the SqlConnection.")]
203                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
204                 public 
205 #if NET_2_0
206                 override
207 #endif // NET_2_0
208                 string ServerVersion {
209                         get { return tds.ServerVersion; }
210                 }
211
212                 [Browsable (false)]
213                 [DataSysDescription ("The ConnectionState indicating whether the connection is open or closed.")]
214                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
215                 public 
216 #if NET_2_0
217                 override
218 #endif // NET_2_0
219                 ConnectionState State {
220                         get { return state; }
221                 }
222
223                 internal ITds Tds {
224                         get { return tds; }
225                 }
226
227                 internal SqlTransaction Transaction {
228                         get { return transaction; }
229                         set { transaction = value; }
230                 }
231
232                 [DataSysDescription ("Workstation Id, 'Workstation Id=x' in the ConnectionString.")]    
233                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
234                 public string WorkstationId {
235                         get { return parms.Hostname; }
236                 }
237
238                 internal XmlReader XmlReader {
239                         get { return xmlReader; }
240                         set { xmlReader = value; }
241                 }
242
243                 #endregion // Properties
244
245                 #region Events
246
247                 [DataCategory ("InfoMessage")]
248                 [DataSysDescription ("Event triggered when messages arrive from the DataSource.")]
249                 public event SqlInfoMessageEventHandler InfoMessage;
250
251                 [DataCategory ("StateChange")]
252                 [DataSysDescription ("Event triggered when the connection changes state.")]
253                 public 
254 #if NET_2_0
255                 override
256 #endif // NET_2_0
257                 event StateChangeEventHandler StateChange;
258                 
259                 #endregion // Events
260
261                 #region Delegates
262
263                 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
264                 {
265                         throw new SqlException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono SqlClient Data Provider", e.State);
266                 }
267
268                 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
269                 {
270                         OnSqlInfoMessage (CreateSqlInfoMessageEvent (e.Errors));
271                 }
272
273                 #endregion // Delegates
274
275                 #region Methods
276                 
277                 internal string GetConnStringKeyValue (params string [] keys)
278                 {
279                         if (connStringParameters == null || connStringParameters.Count == 0)
280                                 return "";
281                         foreach (string key in keys) {
282                                 string value = connStringParameters [key];
283                                 if (value != null)
284                                         return value;
285                         }
286                         
287                         return "";
288                 }
289                 
290
291                 public new SqlTransaction BeginTransaction ()
292                 {
293                         return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
294                 }
295
296                 public new SqlTransaction BeginTransaction (IsolationLevel iso)
297                 {
298                         return BeginTransaction (iso, String.Empty);
299                 }
300
301                 public SqlTransaction BeginTransaction (string transactionName)
302                 {
303                         return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
304                 }
305
306                 public SqlTransaction BeginTransaction (IsolationLevel iso, string transactionName)
307                 {
308                         if (state == ConnectionState.Closed)
309                                 throw new InvalidOperationException ("The connection is not open.");
310                         if (transaction != null)
311                                 throw new InvalidOperationException ("SqlConnection does not support parallel transactions.");
312
313                         if (iso == IsolationLevel.Chaos)
314                                 throw new ArgumentException ("Invalid IsolationLevel parameter: must be ReadCommitted, ReadUncommitted, RepeatableRead, or Serializable.");
315
316                         string isolevel = String.Empty;
317                         switch (iso) {
318                         case IsolationLevel.ReadCommitted:
319                                 isolevel = "READ COMMITTED";
320                                 break;
321                         case IsolationLevel.ReadUncommitted:
322                                 isolevel = "READ UNCOMMITTED";
323                                 break;
324                         case IsolationLevel.RepeatableRead:
325                                 isolevel = "REPEATABLE READ";
326                                 break;
327                         case IsolationLevel.Serializable:
328                                 isolevel = "SERIALIZABLE";
329                                 break;
330                         }
331
332                         tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0};BEGIN TRANSACTION {1}", isolevel, transactionName));
333
334                         transaction = new SqlTransaction (this, iso);
335                         return transaction;
336                 }
337
338                 public 
339 #if NET_2_0
340                 override
341 #endif // NET_2_0
342          void ChangeDatabase (string database) 
343                 {
344                         if (!IsValidDatabaseName (database))
345                                 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
346                         if (state != ConnectionState.Open)
347                                 throw new InvalidOperationException ("The connection is not open.");
348                         tds.Execute (String.Format ("use {0}", database));
349                 }
350
351                 private void ChangeState (ConnectionState currentState)
352                 {
353                         ConnectionState originalState = state;
354                         state = currentState;
355                         OnStateChange (CreateStateChangeEvent (originalState, currentState));
356                 }
357
358                 public 
359 #if NET_2_0
360                 override
361 #endif // NET_2_0
362                 void Close () 
363                 {
364                         if (transaction != null && transaction.IsOpen)
365                                 transaction.Rollback ();
366
367                         if (dataReader != null || xmlReader != null) {
368                                 if(tds != null) tds.SkipToEnd ();
369                                 dataReader = null;
370                                 xmlReader = null;
371                         }
372
373                         if (pooling) {
374                                 if(pool != null) pool.ReleaseConnection (tds);
375                         }else
376                                 if(tds != null) tds.Disconnect ();
377
378                         if(tds != null) {
379                                 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
380                                 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
381                         }
382
383                         ChangeState (ConnectionState.Closed);
384                 }
385
386                 public new SqlCommand CreateCommand () 
387                 {
388                         SqlCommand command = new SqlCommand ();
389                         command.Connection = this;
390                         return command;
391                 }
392                 
393                 private SqlInfoMessageEventArgs CreateSqlInfoMessageEvent (TdsInternalErrorCollection errors)
394                 {
395                         return new SqlInfoMessageEventArgs (errors);
396                 }
397
398                 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
399                 {
400                         return new StateChangeEventArgs (originalState, currentState);
401                 }
402
403                 protected override void Dispose (bool disposing) 
404                 {
405                         if (!disposed) { 
406                                 try {
407                                         if (disposing) {
408                                                 if (State == ConnectionState.Open) 
409                                                         Close ();
410                                                 parms.Reset ();
411                                                 dataSource = ""; // default dataSource
412                                                 ConnectionString = null;
413                                         }
414                                         disposed = true;
415                                 } finally {
416                                         base.Dispose (disposing);
417                                 }
418                                 
419                         }
420                 }
421
422                 [MonoTODO ("Not sure what this means at present.")]
423                 public 
424 #if NET_2_0
425                 override
426 #endif // NET_2_0
427                 void EnlistDistributedTransaction (ITransaction transaction)
428                 {
429                         throw new NotImplementedException ();
430                 }
431
432                 object ICloneable.Clone ()
433                 {
434                         return new SqlConnection (ConnectionString);
435                 }
436
437                 IDbTransaction IDbConnection.BeginTransaction ()
438                 {
439                         return BeginTransaction ();
440                 }
441
442                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
443                 {
444                         return BeginTransaction (iso);
445                 }
446
447                 IDbCommand IDbConnection.CreateCommand ()
448                 {
449                         return CreateCommand ();
450                 }
451
452                 void IDisposable.Dispose ()
453                 {
454                         Dispose (true);
455                         GC.SuppressFinalize (this);
456                 }
457
458                 public 
459 #if NET_2_0
460                 override
461 #endif // NET_2_0
462                 void Open () 
463                 {
464                         string serverName = "";
465                         if (connectionString == null)
466                                 throw new InvalidOperationException ("Connection string has not been initialized.");
467
468                         try {
469                                 if (!pooling) {
470                                         if(!ParseDataSource (dataSource, out port, out serverName))
471                                                 throw new SqlException(20, 0, "SQL Server does not exist or access denied.",  17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
472                                         tds = new Tds70 (serverName, port, PacketSize, ConnectionTimeout);
473                                 }
474                                 else {
475                                         if(!ParseDataSource (dataSource, out port, out serverName))
476                                                 throw new SqlException(20, 0, "SQL Server does not exist or access denied.",  17, "ConnectionOpen (Connect()).", dataSource, parms.ApplicationName, 0);
477                                         
478                                         TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
479                                         pool = sqlConnectionPools.GetConnectionPool (connectionString, info);
480                                         tds = pool.GetConnection ();
481                                 }
482                         }
483                         catch (TdsTimeoutException e) {
484                                 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
485                         }
486
487                         tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
488                         tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
489
490                         if (!tds.IsConnected) {
491                                 try {
492                                         tds.Connect (parms);
493                                 }
494                                 catch {
495                                         if (pooling)
496                                                 pool.ReleaseConnection (tds);
497                                         throw;
498                                 }
499                         }
500
501                         /* Not sure ebout removing these 2 lines.
502                          * The command that gets to the sql server is just
503                          * 'sp_reset_connection' and it fails.
504                          * Either remove them definitely or fix it
505                         else if (connectionReset)
506                                 tds.ExecProc ("sp_reset_connection");
507                         */
508                         disposed = false; // reset this, so using () would call Close ().
509                         ChangeState (ConnectionState.Open);
510                 }
511
512                 private bool ParseDataSource (string theDataSource, out int thePort, out string theServerName) 
513                 {
514                         theServerName = "";
515                         string theInstanceName = "";
516                         if ((theDataSource == null) || (theServerName == null) 
517                             || theDataSource == "")
518                                 throw new ArgumentException("Format of initialization string doesnot conform to specifications");
519                                 
520                         thePort = 1433; // default TCP port for SQL Server
521                         bool success = true;
522
523                         int idx = 0;
524                         if ((idx = theDataSource.IndexOf (",")) > -1) {
525                                 theServerName = theDataSource.Substring (0, idx);
526                                 string p = theDataSource.Substring (idx + 1);
527                                 thePort = Int32.Parse (p);
528                         }
529                         else if ((idx = theDataSource.IndexOf ("\\")) > -1) {
530                                 theServerName = theDataSource.Substring (0, idx);
531                                 theInstanceName = theDataSource.Substring (idx + 1);
532                                 // do port discovery via UDP port 1434
533                                 port = DiscoverTcpPortViaSqlMonitor (theServerName, theInstanceName);
534                                 if (port == -1)
535                                         success = false;
536                         }
537                         else {
538                                 theServerName = theDataSource;
539                         }
540
541                         if(theServerName.Equals("(local)"))
542                                 theServerName = "localhost";
543
544                         return success;
545                 }
546
547                 private bool ConvertIntegratedSecurity (string value)
548                 {
549                         if (value.ToUpper() == "SSPI") 
550                         {
551                                 return true;
552                         }
553
554                         return ConvertToBoolean("integrated security", value);
555                 }
556
557                 private bool ConvertToBoolean(string key, string value)
558                 {
559                         string upperValue = value.ToUpper();
560
561                         if (upperValue == "TRUE" ||upperValue == "YES")
562                         {
563                                 return true;
564                         } 
565                         else if (upperValue == "FALSE" || upperValue == "NO")
566                         {
567                                 return false;
568                         }
569
570                         throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
571                                 "Invalid value \"{0}\" for key '{1}'.", value, key));
572                 }
573
574                 private int ConvertToInt32(string key, string value)
575                 {
576                         try
577                         {
578                                 return int.Parse(value);
579                         }
580                         catch (Exception ex)
581                         {
582                                 throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
583                                         "Invalid value \"{0}\" for key '{1}'.", value, key));
584                         }
585                 }
586
587                 private int DiscoverTcpPortViaSqlMonitor(string ServerName, string InstanceName) 
588                 {
589                         SqlMonitorSocket msock;
590                         msock = new SqlMonitorSocket (ServerName, InstanceName);
591                         int SqlServerPort = msock.DiscoverTcpPort ();
592                         msock = null;
593                         return SqlServerPort;
594                 }
595
596                 void SetConnectionString (string connectionString)
597                 {
598                         if (( connectionString == null)||( connectionString.Length == 0)) {
599                                 this.connectionString = null;
600                                 return;
601                         }
602                         
603                         NameValueCollection parameters = new NameValueCollection ();
604                         connectionString += ";";
605
606                         SetDefaultConnectionParameters (parameters);
607
608                         bool inQuote = false;
609                         bool inDQuote = false;
610                         bool inName = true;
611
612                         string name = String.Empty;
613                         string value = String.Empty;
614                         StringBuilder sb = new StringBuilder ();
615
616                         for (int i = 0; i < connectionString.Length; i += 1) {
617                                 char c = connectionString [i];
618                                 char peek;
619                                 if (i == connectionString.Length - 1)
620                                         peek = '\0';
621                                 else
622                                         peek = connectionString [i + 1];
623
624                                 switch (c) {
625                                 case '\'':
626                                         if (inDQuote)
627                                                 sb.Append (c);
628                                         else if (peek.Equals (c)) {
629                                                 sb.Append (c);
630                                                 i += 1;
631                                         }
632                                         else
633                                                 inQuote = !inQuote;
634                                         break;
635                                 case '"':
636                                         if (inQuote)
637                                                 sb.Append (c);
638                                         else if (peek.Equals (c)) {
639                                                 sb.Append (c);
640                                                 i += 1;
641                                         }
642                                         else
643                                                 inDQuote = !inDQuote;
644                                         break;
645                                 case ';':
646                                         if (inDQuote || inQuote)
647                                                 sb.Append (c);
648                                         else {
649                                                 if (name != String.Empty && name != null) {
650                                                         value = sb.ToString ();
651                                                         parameters [name.ToUpper ().Trim ()] = value.Trim ();
652                                                 }
653                                                 inName = true;
654                                                 name = String.Empty;
655                                                 value = String.Empty;
656                                                 sb = new StringBuilder ();
657                                         }
658                                         break;
659                                 case '=':
660                                         if (inDQuote || inQuote || !inName)
661                                                 sb.Append (c);
662                                         else if (peek.Equals (c)) {
663                                                 sb.Append (c);
664                                                 i += 1;
665                                         }
666                                         else {
667                                                 name = sb.ToString ();
668                                                 sb = new StringBuilder ();
669                                                 inName = false;
670                                         }
671                                         break;
672                                 case ' ':
673                                         if (inQuote || inDQuote)
674                                                 sb.Append (c);
675                                         else if (sb.Length > 0 && !peek.Equals (';'))
676                                                 sb.Append (c);
677                                         break;
678                                 default:
679                                         sb.Append (c);
680                                         break;
681                                 }
682                         }
683                         
684                         SetProperties (parameters);
685
686                         this.connectionString = connectionString;
687                         this.connStringParameters = parameters;
688                 }
689
690                 void SetDefaultConnectionParameters (NameValueCollection parameters)
691                 {
692                         if (null == parameters.Get ("APPLICATION NAME") && null == parameters.Get ("APP"))
693                                 parameters["APPLICATION NAME"] = "Mono SqlClient Data Provider";
694                         if (null == parameters.Get ("TIMEOUT") && null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
695                                 parameters["CONNECT TIMEOUT"] = "15";
696                         if (null == parameters.Get ("CONNECTION LIFETIME"))
697                                 parameters["CONNECTION LIFETIME"] = "0";
698                         if (null == parameters.Get ("CONNECTION RESET"))
699                                 parameters["CONNECTION RESET"] = "true";
700                         if (null == parameters.Get ("ENLIST"))
701                                 parameters["ENLIST"] = "true";
702                         if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
703                                 parameters["INTEGRATED SECURITY"] = "false";
704                         if (null == parameters.Get ("MAX POOL SIZE"))
705                                 parameters["MAX POOL SIZE"] = "100";
706                         if (null == parameters.Get ("MIN POOL SIZE"))
707                                 parameters["MIN POOL SIZE"] = "0";
708                         if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET") && null == parameters.Get ("NETWORK"))
709                                 parameters["NETWORK LIBRARY"] = "dbmssocn";
710                         if (null == parameters.Get ("PACKET SIZE"))
711                                 parameters["PACKET SIZE"] = "512";
712                         if (null == parameters.Get ("PERSIST SECURITY INFO") && null == parameters.Get ("PERSISTSECURITYINFO"))
713                                 parameters["PERSIST SECURITY INFO"] = "false";
714                         if (null == parameters.Get ("POOLING"))
715                                 parameters["POOLING"] = "true";
716                         if (null == parameters.Get ("WORKSTATION ID") && null == parameters.Get ("WSID"))
717                                 parameters["WORKSTATION ID"] = Dns.GetHostName();
718                 }
719
720                 private void SetProperties (NameValueCollection parameters)
721                 {
722                         foreach (string name in parameters) {
723                                 string value = parameters[name];
724
725                                 switch (name) {
726                                         case "APP" :
727                                         case "APPLICATION NAME" :
728                                                 parms.ApplicationName = value;
729                                                 break;
730                                         case "ATTACHDBFILENAME" :
731                                         case "EXTENDED PROPERTIES" :
732                                         case "INITIAL FILE NAME" :
733                                                 throw new NotImplementedException("Attachable database support is not implemented.");
734                                         case "TIMEOUT" :
735                                         case "CONNECT TIMEOUT" :
736                                         case "CONNECTION TIMEOUT" :
737                                                 connectionTimeout = ConvertToInt32 ("connection timeout", value);
738                                                 break;
739                                         case "CONNECTION LIFETIME" :
740                                                 break;
741                                         case "CONNECTION RESET" :
742                                                 connectionReset = ConvertToBoolean ("connection reset", value);
743                                                 break;
744                                         case "LANGUAGE" :
745                                         case "CURRENT LANGUAGE" :
746                                                 parms.Language = value;
747                                                 break;
748                                         case "DATA SOURCE" :
749                                         case "SERVER" :
750                                         case "ADDRESS" :
751                                         case "ADDR" :
752                                         case "NETWORK ADDRESS" :
753                                                 dataSource = value;
754                                                 break;
755                                         case "ENCRYPT":
756                                                 if (ConvertToBoolean("encrypt", value))
757                                                 {
758                                                         throw new NotImplementedException("SSL encryption for"
759                                                                 + " data sent between client and server is not"
760                                                                 + " implemented.");
761                                                 }
762                                                 break;
763                                         case "ENLIST" :
764                                                 if (!ConvertToBoolean("enlist", value))
765                                                 {
766                                                         throw new NotImplementedException("Disabling the automatic"
767                                                                 + " enlistment of connections in the thread's current"
768                                                                 + " transaction context is not implemented.");
769                                                 }
770                                                 break;
771                                         case "INITIAL CATALOG" :
772                                         case "DATABASE" :
773                                                 parms.Database = value;
774                                                 break;
775                                         case "INTEGRATED SECURITY" :
776                                         case "TRUSTED_CONNECTION" :
777                                                 parms.DomainLogin = ConvertIntegratedSecurity(value);
778                                                 break;
779                                         case "MAX POOL SIZE" :
780                                                 maxPoolSize = ConvertToInt32 ("max pool size", value);
781                                                 break;
782                                         case "MIN POOL SIZE" :
783                                                 minPoolSize = ConvertToInt32 ("min pool size", value);
784                                                 break;
785 #if NET_2_0
786                                 case "MULTIPLEACTIVERESULTSETS":
787                                         break;
788 #endif
789                                         case "NET" :
790                                         case "NETWORK" :
791                                         case "NETWORK LIBRARY" :
792                                                 if (!value.ToUpper ().Equals ("DBMSSOCN"))
793                                                         throw new ArgumentException ("Unsupported network library.");
794                                                 break;
795                                         case "PACKET SIZE" :
796                                                 packetSize = ConvertToInt32 ("packet size", value);
797                                                 break;
798                                         case "PASSWORD" :
799                                         case "PWD" :
800                                                 parms.Password = value;
801                                                 break;
802                                         case "PERSISTSECURITYINFO" :
803                                         case "PERSIST SECURITY INFO" :
804                                                 // FIXME : not implemented
805                                                 break;
806                                         case "POOLING" :
807                                                 pooling = ConvertToBoolean("pooling", value);
808                                                 break;
809                                         case "UID" :
810                                         case "USER" :
811                                         case "USER ID" :
812                                                 parms.User = value;
813                                                 break;
814                                         case "WSID" :
815                                         case "WORKSTATION ID" :
816                                                 parms.Hostname = value;
817                                                 break;
818                                         default :
819                                                 throw new ArgumentException("Keyword not supported :"+name);
820                                 }
821                         }
822                 }
823
824                 static bool IsValidDatabaseName (string database)
825                 {
826                         if (database.Length > 32 || database.Length < 1)
827                                 return false;
828
829                         if (database[0] == '"' && database[database.Length] == '"')
830                                 database = database.Substring (1, database.Length - 2);
831                         else if (Char.IsDigit (database[0]))
832                                 return false;
833
834                         if (database[0] == '_')
835                                 return false;
836
837                         foreach (char c in database.Substring (1, database.Length - 1))
838                                 if (!Char.IsLetterOrDigit (c) && c != '_')
839                                         return false;
840                         return true;
841                 }
842
843                 private void OnSqlInfoMessage (SqlInfoMessageEventArgs value)
844                 {
845                         if (InfoMessage != null)
846                                 InfoMessage (this, value);
847                 }
848
849                 private void OnStateChange (StateChangeEventArgs value)
850                 {
851                         if (StateChange != null)
852                                 StateChange (this, value);
853                 }
854
855                 private sealed class SqlMonitorSocket : UdpClient 
856                 {
857                         // UDP port that the SQL Monitor listens
858                         private static readonly int SqlMonitorUdpPort = 1434;
859                         private static readonly string SqlServerNotExist = "SQL Server does not exist or access denied";
860
861                         private string server;
862                         private string instance;
863
864                         internal SqlMonitorSocket (string ServerName, string InstanceName) 
865                                 : base (ServerName, SqlMonitorUdpPort) 
866                         {
867                                 server = ServerName;
868                                 instance = InstanceName;
869                         }
870
871                         internal int DiscoverTcpPort () 
872                         {
873                                 int SqlServerTcpPort;
874                                 Client.Blocking = false;
875                                 // send command to UDP 1434 (SQL Monitor) to get
876                                 // the TCP port to connect to the MS SQL server         
877                                 ASCIIEncoding enc = new ASCIIEncoding ();
878                                 Byte[] rawrq = new Byte [instance.Length + 1];
879                                 rawrq[0] = 4;
880                                 enc.GetBytes (instance, 0, instance.Length, rawrq, 1);
881                                 int bytes = Send (rawrq, rawrq.Length);
882
883                                 if (!Active)
884                                         return -1; // Error
885                                 
886                                 bool result;
887                                 result = Client.Poll (100, SelectMode.SelectRead);
888                                 if (result == false)
889                                         return -1; // Error
890
891                                 if (Client.Available <= 0)
892                                         return -1; // Error
893
894                                 IPEndPoint endpoint = new IPEndPoint (Dns.GetHostByName ("localhost").AddressList [0], 0);
895                                 Byte [] rawrs;
896
897                                 rawrs = Receive (ref endpoint);
898
899                                 string rs = Encoding.ASCII.GetString (rawrs);
900
901                                 string[] rawtokens = rs.Split (';');
902                                 Hashtable data = new Hashtable ();
903                                 for (int i = 0; i < rawtokens.Length / 2 && i < 256; i++) {
904                                         data [rawtokens [i * 2]] = rawtokens [ i * 2 + 1];
905                                 }
906                                 if (!data.ContainsKey ("tcp")) 
907                                         throw new NotImplementedException ("Only TCP/IP is supported.");
908
909                                 SqlServerTcpPort = int.Parse ((string) data ["tcp"]);
910                                 Close ();
911
912                                 return SqlServerTcpPort;
913                         }
914                 }
915
916                 #endregion // Methods
917         }
918 }