New tests + updates
[mono.git] / mcs / class / System.Data / System.Data.Odbc / OdbcConnection.cs
1 //
2 // System.Data.Odbc.OdbcConnection
3 //
4 // Authors:
5 //  Brian Ritchie (brianlritchie@hotmail.com) 
6 //
7 // Copyright (C) Brian Ritchie, 2002
8 //
9
10 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System.ComponentModel;
34 using System.Data;
35 using System.Data.Common;
36 using System.Runtime.InteropServices;
37 using System.EnterpriseServices;
38 #if NET_2_0 && !TARGET_JVM
39 using System.Transactions;
40 #endif
41
42 namespace System.Data.Odbc
43 {
44         [DefaultEvent ("InfoMessage")]
45 #if NET_2_0
46         public sealed class OdbcConnection : DbConnection, ICloneable
47 #else
48         public sealed class OdbcConnection : Component, ICloneable, IDbConnection
49 #endif //NET_2_0
50         {
51                 #region Fields
52
53                 string connectionString;
54                 int connectionTimeout;
55                 internal OdbcTransaction transaction;
56                 IntPtr henv=IntPtr.Zero, hdbc=IntPtr.Zero;
57                 bool disposed = false;                  
58                 
59                 #endregion
60
61                 #region Constructors
62                 
63                 public OdbcConnection () : this (String.Empty)
64                 {
65                 }
66
67                 public OdbcConnection (string connectionString)
68                 {
69                         Init (connectionString);
70                 }
71
72                 private void Init (string connectionString)
73                 {
74                         connectionTimeout = 15;
75                         ConnectionString = connectionString;
76                 }
77
78                 #endregion // Constructors
79
80                 #region Properties
81
82                 internal IntPtr hDbc
83                 {
84                         get { return hdbc; }
85                 }
86
87                 [OdbcCategoryAttribute ("DataCategory_Data")]           
88                 [DefaultValue ("")]
89                 [OdbcDescriptionAttribute ("Information used to connect to a Data Source")]     
90                 [RefreshPropertiesAttribute (RefreshProperties.All)]
91                 [EditorAttribute ("Microsoft.VSDesigner.Data.Odbc.Design.OdbcConnectionStringEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
92                 [RecommendedAsConfigurableAttribute (true)]
93                 public 
94 #if NET_2_0
95                 override
96 #endif
97                 string ConnectionString {
98                         get {
99                                 return connectionString;
100                         }
101                         set {
102                                 connectionString = value;
103                         }
104                 }
105                 
106                 [OdbcDescriptionAttribute ("Current connection timeout value, not settable  in the ConnectionString")]
107                 [DefaultValue (15)]
108 #if NET_2_0
109                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
110 #endif
111                 public
112 #if NET_2_0
113                 new
114 #endif // NET_2_0
115                 int ConnectionTimeout {
116                         get {
117                                 return connectionTimeout;
118                         }
119                         set {
120                                 if (value < 0) {
121                                         throw new ArgumentException("Timout should not be less than zero.");
122                                 }
123                                 connectionTimeout = value;
124                         }
125                 }
126
127                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
128                 [OdbcDescriptionAttribute ("Current data source Catlog value, 'Database=X' in the ConnectionString")]
129                 public
130 #if NET_2_0
131                 override
132 #endif // NET_2_0
133                 string Database {
134                         get {
135                                 return GetInfo (OdbcInfo.DatabaseName);
136                         }
137                 }
138
139                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
140                 [OdbcDescriptionAttribute ("The ConnectionState indicating whether the connection is open or closed")]
141                 [BrowsableAttribute (false)]            
142                 public
143 #if NET_2_0
144                 override
145 #endif // NET_2_0
146                 ConnectionState State
147                 {
148                         get {
149                                 if (hdbc!=IntPtr.Zero) {
150                                         return ConnectionState.Open;
151                                 }
152                                 else
153                                         return ConnectionState.Closed;
154                         }
155                 }
156
157                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
158                 [OdbcDescriptionAttribute ("Current data source, 'Server=X' in the ConnectionString")]
159 #if NET_2_0
160                 [Browsable (false)]
161 #endif
162                 public
163 #if NET_2_0
164                 override
165 #endif // NET_2_0
166                 string DataSource {
167                         get {
168                                 return GetInfo (OdbcInfo.DataSourceName);
169                         }
170                 }
171
172 #if NET_2_0
173                 [Browsable (false)]
174 #endif
175                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
176                 [OdbcDescriptionAttribute ("Current ODBC Driver")]
177                 public string Driver {
178                         get {
179                                 return GetInfo (OdbcInfo.DriverName);
180                         }
181                 }
182                 
183                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
184                 [OdbcDescriptionAttribute ("Version of the product accessed by the ODBC Driver")]
185                 [BrowsableAttribute (false)]
186                 public
187 #if NET_2_0
188                 override
189 #endif // NET_2_0
190                 string ServerVersion {
191                         get {
192                                 return GetInfo (OdbcInfo.DbmsVersion);
193                         }
194                 }
195
196                 
197                 #endregion // Properties
198         
199                 #region Methods
200         
201                 public
202 #if NET_2_0
203                 new
204 #endif // NET_2_0
205                 OdbcTransaction BeginTransaction ()
206                 {
207                         return BeginTransaction(IsolationLevel.Unspecified);
208                 }
209
210 #if ONLY_1_1              
211                 IDbTransaction IDbConnection.BeginTransaction ()
212                 {
213                         return (IDbTransaction) BeginTransaction();
214                 }
215 #endif // ONLY_1_1
216 #if NET_2_0
217                 protected override DbTransaction BeginDbTransaction (IsolationLevel level)
218                 {
219                         return BeginTransaction (level);
220                 }
221 #endif
222                 
223                 public
224 #if NET_2_0
225                 new
226 #endif // NET_2_0
227                 OdbcTransaction BeginTransaction (IsolationLevel level)
228                 {
229                         if (transaction==null)
230                         {
231                                 transaction=new OdbcTransaction(this,level);
232                                 return transaction;
233                         }
234                         else
235                                 throw new InvalidOperationException();
236                 }
237
238 #if ONLY_1_1
239                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel level)
240                 {
241                         return (IDbTransaction) BeginTransaction(level);
242                 }
243 #endif // ONLY_1_1
244
245                 public
246 #if NET_2_0
247                 override
248 #endif // NET_2_0
249                 void Close ()
250                 {
251                         OdbcReturn ret = OdbcReturn.Error;
252                         if (State == ConnectionState.Open) {
253                                 // disconnect
254                                 ret = libodbc.SQLDisconnect (hdbc);
255                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) {
256                                         throw new OdbcException (new OdbcError ("SQLDisconnect", OdbcHandleType.Dbc, hdbc));
257                                 }
258
259                                 FreeHandles ();
260
261                                 transaction = null;
262
263                                 RaiseStateChange (ConnectionState.Open, ConnectionState.Closed);
264                         }
265                 }
266
267                 public
268 #if NET_2_0
269                 new
270 #endif // NET_2_0
271                 OdbcCommand CreateCommand ()
272                 {
273                         return new OdbcCommand ("", this, transaction); 
274                 }
275
276                 public
277 #if NET_2_0
278                 override
279 #endif // NET_2_0
280                 void ChangeDatabase(string Database)
281                 {
282                         IntPtr ptr = IntPtr.Zero;
283                         OdbcReturn ret = OdbcReturn.Error;
284
285                         try {
286                                 ptr = Marshal.StringToHGlobalAnsi (Database);
287                                 ret = libodbc.SQLSetConnectAttr (hdbc, OdbcConnectionAttribute.CurrentCatalog, ptr, Database.Length);
288
289                                 if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo)
290                                         throw new OdbcException (new OdbcError ("SQLSetConnectAttr", OdbcHandleType.Dbc, hdbc));
291                         } finally {
292                                 if (ptr != IntPtr.Zero)
293                                         Marshal.FreeCoTaskMem (ptr);
294                         }
295                 }
296
297                 protected override void Dispose (bool disposing)
298                 {
299                         if (!this.disposed) {
300                                 try 
301                                 {
302                                         // release the native unmananged resources
303                                         this.Close();
304                                         this.disposed = true;
305                                 }
306                                 finally 
307                                 {
308                                         // call Dispose on the base class
309                                         base.Dispose(disposing);                        
310                                 }
311                         }
312                 }
313
314                 [MonoTODO]
315                 object ICloneable.Clone ()
316                 {
317                         throw new NotImplementedException();
318                 }
319
320 #if ONLY_1_1
321                 IDbCommand IDbConnection.CreateCommand ()
322                 {
323                         return (IDbCommand) CreateCommand ();
324                 }
325 #endif //ONLY_1_1
326
327 #if NET_2_0
328                 protected override DbCommand CreateDbCommand ()
329                 {
330                         return CreateCommand ();
331                 }
332 #endif
333
334                 public
335 #if NET_2_0
336                 override
337 #endif // NET_2_0
338                 void Open ()
339                 {
340                         if (State == ConnectionState.Open)
341                                 throw new InvalidOperationException ();
342
343                         OdbcReturn ret = OdbcReturn.Error;
344                         OdbcException e = null;
345                 
346                         try {
347                                 // allocate Environment handle
348                                 ret = libodbc.SQLAllocHandle (OdbcHandleType.Env, IntPtr.Zero, ref henv);
349                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) {
350                                         e = new OdbcException (new OdbcError ("SQLAllocHandle"));
351                                         MessageHandler (e);
352                                         throw e;
353                                 }
354
355                                 ret = libodbc.SQLSetEnvAttr (henv, OdbcEnv.OdbcVersion, (IntPtr) libodbc.SQL_OV_ODBC3 , 0); 
356                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
357                                         throw new OdbcException (new OdbcError ("SQLSetEnvAttr", OdbcHandleType.Env, henv));
358
359                                 // allocate connection handle
360                                 ret = libodbc.SQLAllocHandle (OdbcHandleType.Dbc, henv, ref hdbc);
361                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
362                                         throw new OdbcException (new OdbcError ("SQLAllocHandle", OdbcHandleType.Env, henv));
363
364                                 // DSN connection
365                                 if (ConnectionString.ToLower ().IndexOf ("dsn=") >= 0)
366                                 {
367                                         string _uid = "", _pwd = "", _dsn = "";
368                                         string [] items = ConnectionString.Split (new char[1]{';'});
369                                         foreach (string item in items)
370                                         {
371                                                 string [] parts = item.Split (new char[1] {'='});
372                                                 switch (parts [0].Trim ().ToLower ())
373                                                 {
374                                                 case "dsn":
375                                                         _dsn = parts [1].Trim ();
376                                                         break;
377                                                 case "uid":
378                                                         _uid = parts [1].Trim ();
379                                                         break;
380                                                 case "pwd":
381                                                         _pwd = parts [1].Trim ();
382                                                         break;
383                                                 }
384                                         }
385                                         ret = libodbc.SQLConnect(hdbc, _dsn, -3, _uid, -3, _pwd, -3);
386                                         if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) 
387                                                 throw new OdbcException (new OdbcError ("SQLConnect", OdbcHandleType.Dbc, hdbc));
388                                 }
389                                 else 
390                                 {
391                                         // DSN-less Connection
392                                         string OutConnectionString = new String (' ',1024);
393                                         short OutLen = 0;
394                                         ret = libodbc.SQLDriverConnect (hdbc, IntPtr.Zero, ConnectionString, -3, 
395                                                                      OutConnectionString, (short) OutConnectionString.Length, ref OutLen, 0);
396                                         if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) 
397                                                 throw new OdbcException (new OdbcError ("SQLDriverConnect", OdbcHandleType.Dbc, hdbc));
398                                 }
399
400                                 RaiseStateChange (ConnectionState.Closed, ConnectionState.Open);
401                         } catch (Exception) {
402                                 // free handles if any.
403                                 FreeHandles ();
404                                 throw;
405                         }
406                         disposed = false;
407                 }
408
409                 [MonoTODO]
410                 public static void ReleaseObjectPool ()
411                 {
412                         throw new NotImplementedException ();
413                 }
414
415                 private void FreeHandles ()
416                 {
417                         OdbcReturn ret = OdbcReturn.Error;
418                         if (hdbc != IntPtr.Zero) {
419                                 ret = libodbc.SQLFreeHandle ((ushort) OdbcHandleType.Dbc, hdbc);
420                                 if ( (ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) 
421                                         throw new OdbcException (new OdbcError ("SQLFreeHandle", OdbcHandleType.Dbc, hdbc));
422                         }
423                         hdbc = IntPtr.Zero;
424
425                         if (henv != IntPtr.Zero) {
426                                 ret = libodbc.SQLFreeHandle ((ushort) OdbcHandleType.Env, henv);
427                                 if ( (ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) 
428                                         throw new OdbcException (new OdbcError ("SQLFreeHandle", OdbcHandleType.Env, henv));
429                         }
430                         henv = IntPtr.Zero;
431                 }
432
433 #if NET_2_0
434                 public new DataTable GetSchema ()
435                 {
436                         if (State == ConnectionState.Open)
437                                 throw new InvalidOperationException ();
438                         return MetaDataCollections.Instance;
439                 }
440
441                 public new DataTable GetSchema (string collectionName)
442                 {
443                         if (State == ConnectionState.Open)
444                                 throw new InvalidOperationException ();
445                         return GetSchema (collectionName, null);
446                 }
447
448                 [MonoTODO]
449                 public override void EnlistTransaction (Transaction transaction)
450                 {
451                         throw new NotImplementedException ();
452                 }
453 #endif
454
455                 [MonoTODO]
456                 public void EnlistDistributedTransaction ( ITransaction transaction) 
457                 {
458                         throw new NotImplementedException ();
459                 }
460
461                 internal string GetInfo (OdbcInfo info)
462                 {
463                         if (State == ConnectionState.Closed)
464                                 throw new InvalidOperationException ("The connection is closed.");
465                         
466                         OdbcReturn ret = OdbcReturn.Error;
467                         short max_length = 256;
468                         byte [] buffer = new byte [max_length];
469                         short actualLength = 0;
470                         
471                         ret = libodbc.SQLGetInfo (hdbc, info, buffer, max_length, ref actualLength);
472                         if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo)
473                                 throw new OdbcException (new OdbcError ("SQLGetInfo",
474                                                                         OdbcHandleType.Dbc,
475                                                                         hdbc));
476
477                         return System.Text.Encoding.Default.GetString (buffer);
478                 }
479
480                 private void RaiseStateChange (ConnectionState from, ConnectionState to)
481                 {
482 #if ONLY_1_1
483                         if (StateChange != null)
484                                 StateChange (this, new StateChangeEventArgs (from, to));
485 #else
486                         base.OnStateChange (new StateChangeEventArgs (from, to));
487 #endif
488                 }
489
490                 private OdbcInfoMessageEventArgs CreateOdbcInfoMessageEvent (OdbcErrorCollection errors)
491                 {
492                         return new OdbcInfoMessageEventArgs (errors);
493                 }
494
495                 private void OnOdbcInfoMessage (OdbcInfoMessageEventArgs e) {
496                         if (null != InfoMessage) {
497                                 InfoMessage (this, e);
498                         }
499                 }
500
501                 #endregion
502
503                 #region Events and Delegates
504
505 #if ONLY_1_1
506                 [OdbcDescription ("DbConnection_StateChange")]
507                 [OdbcCategory ("DataCategory_StateChange")]
508                 public event StateChangeEventHandler StateChange;
509 #endif // ONLY_1_1
510
511                 [OdbcDescription ("DbConnection_InfoMessage")]
512                 [OdbcCategory ("DataCategory_InfoMessage")]
513                 public event OdbcInfoMessageEventHandler InfoMessage;
514
515                 private void MessageHandler (OdbcException e)
516                 {
517                         OnOdbcInfoMessage (CreateOdbcInfoMessageEvent (e.Errors));
518                 }
519
520                 #endregion
521         }
522 }