New file
[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 //
8 // (C) Ximian, Inc 2002
9 //
10
11 // use #define DEBUG_SqlConnection if you want to spew debug messages
12 // #define DEBUG_SqlConnection
13
14 using System;
15 using System.ComponentModel;
16 using System.Data;
17 using System.Data.Common;
18 using System.Runtime.InteropServices;
19 using System.Text;
20
21 namespace System.Data.SqlClient
22 {
23         // using PGconn = IntPtr; 
24         // PGconn is native C library type in libpq for Postgres Connection
25
26         // using PGressult = IntPtr;
27         // PGresult is native C library type in libpq for Postgres Resultset
28
29         /// <summary>
30         /// Represents an open connection to a SQL data source
31         /// </summary>
32         //public sealed class SqlConnection : Component, IDbConnection,
33         //      ICloneable
34         public sealed class SqlConnection : IDbConnection
35         {
36                 // FIXME: Need to implement class Component, \r
37                 // and interfaces: ICloneable and IDisposable   
38
39                 #region Fields
40
41                 private IntPtr pgConn = IntPtr.Zero;    
42                 // PGConn (Postgres Connection)
43                 private string connectionString = "";    
44                 // OLE DB Connection String
45                 private string pgConnectionString = ""; 
46                 // PostgreSQL Connection String
47                 private SqlTransaction trans = null;
48                 private int connectionTimeout = 15;     
49                 // default for 15 seconds
50                 
51                 // connection parameters in connection string
52                 private string host = "";     
53                 // Name of host to connect to
54                 private string hostaddr = ""; 
55                 // IP address of host to connect to
56                 // should be in "n.n.n.n" format
57                 private string port = "";     
58                 // Port number to connect to at the server host
59                 private string dbname = "";   // The database name. 
60                 private string user = "";     // User name to connect as. 
61                 private string password = "";
62                 // Password to be used if the server 
63                 // demands password authentication.             
64                 private string options = ""; 
65                 // Trace/debug options to be sent to the server. 
66                 private string tty = ""; 
67                 // A file or tty for optional 
68                 // debug output from the backend. 
69                 private string requiressl = "";
70                 // Set to 1 to require 
71                 // SSL connection to the backend. 
72                 // Libpq will then refuse to connect 
73                 // if the server does not 
74                 // support SSL. Set to 0 (default) to 
75                 // negotiate with server. 
76
77                 ConnectionState conState = ConnectionState.Closed;
78
79                 #endregion // Fields
80
81                 #region Constructors
82
83                 /*
84                 [MonoTODO]
85                 public SqlConnection () 
86                 {
87                         this.ConnectionString = null;
88                         this.ConnectionTimeout = 0;
89                         this.Database = null;
90                         this.State = 0;
91                 }
92                 
93                 [MonoTODO]
94                 public SqlConnection (string cs) : SqlConnection () 
95                 {
96                         this.ConnectionString = cs;
97                 }
98                 
99                 */
100                 // A lot of the defaults were initialized in the Fields
101                 [MonoTODO]
102                 public SqlConnection ()
103                 {
104
105                 }
106         
107                 [MonoTODO]
108                 public SqlConnection (String connectionString)
109                 {
110                         SetConnectionString (connectionString);
111                 }
112
113                 #endregion // Constructors
114
115                 #region Destructors
116
117                 [MonoTODO]
118                 public void Dispose () {        
119                         // FIXME: release resources properly
120                         Close ();
121                         // Dispose (true);
122                 }
123         
124                 // aka Finalize
125                 // [ClassInterface(ClassInterfaceType.AutoDual)]
126                 [MonoTODO]
127                 ~SqlConnection()
128                 {
129                         // FIXME: this class need 
130                         //        a destructor to release resources
131                         //        Also, take a look at Dispose
132                         // Dispose (false);
133                 }
134                 
135                 #endregion // Destructors
136
137                 #region Public Methods
138
139                 IDbTransaction IDbConnection.BeginTransaction ()
140                 {
141                         return BeginTransaction ();
142                 }
143
144                 public SqlTransaction BeginTransaction ()
145                 {
146                         return TransactionBegin (); // call private method
147                 }
148
149                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel 
150                                                 il)
151                 {
152                         return BeginTransaction (il);
153                 }
154
155                 public SqlTransaction BeginTransaction (IsolationLevel il)
156                 {
157                         return TransactionBegin (il); // call private method
158                 }
159
160                 // PostgreSQL does not support named transactions/savepoint
161                 //            nor nested transactions
162                 [Obsolete]
163                 public SqlTransaction BeginTransaction(string transactionName) {
164                         return TransactionBegin (); // call private method
165                 }
166
167                 [Obsolete]
168                 public SqlTransaction BeginTransaction(IsolationLevel iso,\r
169                                                 string transactionName) {
170                         return TransactionBegin (iso); // call private method
171                 }
172
173                 [MonoTODO]
174                 public void ChangeDatabase (string databaseName)
175                 {
176                         throw new NotImplementedException ();
177                 }
178                                 
179                 [MonoTODO]
180                 public void Close ()
181                 {
182                         CloseDataSource ();
183                 }
184
185                 IDbCommand IDbConnection.CreateCommand ()
186                 {
187                         return CreateCommand ();
188                 }
189
190                 public SqlCommand CreateCommand ()
191                 {
192                         SqlCommand sqlcmd = new SqlCommand ("", this);
193
194                         return sqlcmd;
195                 }
196
197                 [MonoTODO]
198                 public void Open ()
199                 {
200                         OpenDataSource ();
201                 }
202
203                 #endregion // Public Methods
204
205                 #region Internal Methods
206
207                 // this is for System.Data.SqlClient classes
208                 // to get the Postgres connection
209                 internal IntPtr PostgresConnection {
210                         get {
211                                 return pgConn;
212                         }
213                 }
214
215                 #endregion // Internal Methods
216
217                 #region Protected Methods
218
219                 // FIXME: protected override void Dispose overrides Component
220                 //        however, including Component causes other problems
221                 /*
222                 [MonoTODO]
223                 protected override void Dispose (bool disposing)
224                 {
225                         throw new NotImplementedException ();
226                 }
227                 */
228
229                 #endregion
230
231                 #region Private Methods
232
233                 private void OpenDataSource ()
234                 {
235                         if(dbname.Equals(""))
236                                 throw new InvalidOperationException(
237                                         "dbname missing");
238                         else if(conState == ConnectionState.Open)
239                                 throw new InvalidOperationException(
240                                         "ConnnectionState is already Open");
241
242                         ConnStatusType connStatus;
243
244                         // FIXME: check to make sure we have 
245                         //        everything to connect,
246                         //        otherwise, throw an exception
247
248                         pgConn = PostgresLibrary.PQconnectdb 
249                                         (pgConnectionString);
250
251                         // FIXME: should we use PQconnectStart/PQconnectPoll
252                         //        instead of PQconnectdb?  
253                         // PQconnectdb blocks 
254                         // PQconnectStart/PQconnectPoll is non-blocking
255                         
256                         connStatus = PostgresLibrary.PQstatus (pgConn);
257                         if(connStatus == ConnStatusType.CONNECTION_OK)
258                         {
259                                 // Successfully Connected
260                                 conState = ConnectionState.Open;
261                         }
262                         else
263                         {
264                                 String errorMessage = PostgresLibrary.
265                                         PQerrorMessage (pgConn);
266                                 errorMessage += ": Could not connect to database.";
267
268                                 throw new SqlException(0, 0,
269                                         errorMessage, 0, "",
270                                         host, "SqlConnection", 0);
271                         }
272                 }
273
274                 private void CloseDataSource ()
275                 {
276                         // FIXME: just a quick hack
277                         conState = ConnectionState.Closed;
278                         PostgresLibrary.PQfinish (pgConn);
279                 }
280
281                 private void SetConnectionString (string connectionString)
282                 {
283                         // FIXME: perform error checking on string
284                         // while translating string from 
285                         // OLE DB format to PostgreSQL 
286                         // connection string format
287                         //
288         //     OLE DB: "host=localhost;dbname=test;user=joe;password=smoe"
289         // PostgreSQL: "host=localhost dbname=test user=joe password=smoe"
290                         //
291                         // For OLE DB, you would have the additional 
292                         // "provider=postgresql"
293                         // OleDbConnection you would be using libgda, maybe
294                         // it would be 
295                         // "provider=OAFIID:GNOME_Database_Postgres_Provider"
296                         // instead.
297                         //
298                         // Also, parse the connection string into properties
299
300                         // FIXME: if connection is open, you can 
301                         //        not set the connection
302                         //        string, throw an exception
303
304                         this.connectionString = connectionString;
305                         pgConnectionString = ConvertStringToPostgres (
306                                 connectionString);
307
308 #if DEBUG_SqlConnection
309                         Console.WriteLine(
310                                 "OLE-DB Connection String    [in]: " +
311                                 this.ConnectionString);
312                         Console.WriteLine(
313                                 "Postgres Connection String [out]: " +
314                                 pgConnectionString);
315 #endif // DEBUG_SqlConnection
316                 }
317
318                 private String ConvertStringToPostgres (String 
319                         oleDbConnectionString)
320                 {
321                         StringBuilder postgresConnection = 
322                                 new StringBuilder();
323                         string result;
324                         string[] connectionParameters;
325
326                         char[] semicolon = new Char[1];
327                         semicolon[0] = ';';
328                         
329                         // FIXME: what is the max number of value pairs 
330                         //        can there be for the OLE DB 
331                         //        connnection string? what about libgda max?
332                         //        what about postgres max?
333
334                         // FIXME: currently assuming value pairs are like:
335                         //        "key1=value1;key2=value2;key3=value3"
336                         //        Need to deal with values that have
337                         //        single or double quotes.  And error 
338                         //        handling of that too.
339                         //        "key1=value1;key2='value2';key=\"value3\""
340
341                         // FIXME: put the connection parameters 
342                         //        from the connection
343                         //        string into a 
344                         //        Hashtable (System.Collections)
345                         //        instead of using private variables 
346                         //        to store them
347                         connectionParameters = oleDbConnectionString.
348                                 Split (semicolon);
349                         foreach (string sParameter in connectionParameters) {
350                                 if(sParameter.Length > 0) {
351                                         BreakConnectionParameter (sParameter);
352                                         postgresConnection.
353                                                 Append (sParameter + 
354                                                         " ");
355                                 }
356                         }
357                         result = postgresConnection.ToString ();
358                         return result;
359                 }
360
361                 private bool BreakConnectionParameter (String sParameter)
362                 {       
363                         bool addParm = true;
364                         int index;
365
366                         index = sParameter.IndexOf ("=");
367                         if (index > 0) {        
368                                 string parmKey, parmValue;
369
370                                 // separate string "key=value" to 
371                                 // string "key" and "value"
372                                 parmKey = sParameter.Substring (0, index);
373                                 parmValue = sParameter.Substring (index + 1,
374                                         sParameter.Length - index - 1);
375
376                                 switch(parmKey.ToLower()) {
377                                 case "hostaddr":
378                                         hostaddr = parmValue;
379                                         break;
380
381                                 case "port":
382                                         port = parmValue;
383                                         break;
384
385                                 case "host":
386                                         // set DataSource property
387                                         host = parmValue;
388                                         break;
389
390                                 case "dbname":
391                                         // set Database property
392                                         dbname = parmValue;
393                                         break;
394
395                                 case "user":
396                                         user = parmValue;
397                                         break;
398
399                                 case "password":
400                                         password = parmValue;
401                                 //      addParm = false;
402                                         break;
403
404                                 case "options":
405                                         options = parmValue;
406                                         break;
407
408                                 case "tty":
409                                         tty = parmValue;
410                                         break;
411                                                         
412                                 case "requiressl":
413                                         requiressl = parmValue;
414                                         break;
415                                 }
416                         }
417                         return addParm;
418                 }
419
420                 private SqlTransaction TransactionBegin ()
421                 {
422                         // FIXME: need to keep track of 
423                         // transaction in-progress
424                         trans = new SqlTransaction ();
425                         // using internal methods of SqlTransaction
426                         trans.SetConnection (this);
427                         trans.Begin();
428
429                         return trans;
430                 }
431
432                 private SqlTransaction TransactionBegin (IsolationLevel il)
433                 {
434                         // FIXME: need to keep track of 
435                         // transaction in-progress
436                         TransactionBegin();
437                         trans.SetIsolationLevel (il);
438                         
439                         return trans;
440                 }
441
442                 #endregion
443
444                 #region Properties
445
446                 [MonoTODO]
447                 public ConnectionState State            {
448                         get { 
449                                 return conState;
450                         }
451                 }
452
453                 public string ConnectionString  {
454                         get { 
455                                 return connectionString;
456                         }
457                         set { 
458                                 SetConnectionString (value);
459                         }
460                 }
461                 
462                 public int ConnectionTimeout {
463                         get { 
464                                 return connectionTimeout; 
465                         }
466                 }
467
468                 public string Database  {
469                         get { 
470                                 return dbname; 
471                         }
472                 }
473
474                 public string DataSource {
475                         get {
476                                 return host;
477                         }
478                 }
479
480                 /*
481                  * FIXME: this is here because of Component?
482                 [MonoTODO]
483                 protected bool DesignMode {
484                         get { 
485                                 throw new NotImplementedException (); 
486                         }
487                 }
488                 */
489                 public int PacketSize {
490                         get { 
491                                 throw new NotImplementedException ();
492                         }
493                 }
494
495                 public string ServerVersion {
496                         get { 
497                                 throw new NotImplementedException ();
498                         }
499                 }
500
501                 internal SqlTransaction Transaction {
502                         get {
503                                 return trans;
504                         }
505                 }
506
507                 #endregion
508
509                 #region Events and Delegates
510                 
511                 // FIXME: the two events belong here
512                 // however, i do not know about the delegates
513                 // also, they are stubs for now
514                 /*
515                 public delegate void 
516                 SqlInfoMessageEventHandler (object sender,      
517                                 SqlInfoMessageEventArgs e);
518
519                 public event 
520                 SqlInfoMessageEventHandler InfoMessage;
521
522                 public event 
523                 StateChangeEventHandler StateChange;
524                 */
525
526                 #endregion
527         }
528 }