* OdbcCommandTest.cs: Added test for bug #341743.
[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.Collections;
34 using System.ComponentModel;
35 using System.Data;
36 using System.Data.Common;
37 using System.EnterpriseServices;
38 using System.Runtime.InteropServices;
39 using System.Text;
40 #if NET_2_0 && !TARGET_JVM
41 using System.Transactions;
42 #endif
43
44 namespace System.Data.Odbc
45 {
46         [DefaultEvent ("InfoMessage")]
47 #if NET_2_0
48         public sealed class OdbcConnection : DbConnection, ICloneable
49 #else
50         public sealed class OdbcConnection : Component, ICloneable, IDbConnection
51 #endif //NET_2_0
52         {
53                 #region Fields
54
55                 string connectionString;
56                 int connectionTimeout;
57                 internal OdbcTransaction transaction;
58                 IntPtr henv = IntPtr.Zero;
59                 IntPtr hdbc = IntPtr.Zero;
60                 bool disposed;
61                 ArrayList linkedCommands;
62
63                 #endregion
64
65                 #region Constructors
66                 
67                 public OdbcConnection () : this (String.Empty)
68                 {
69                 }
70
71                 public OdbcConnection (string connectionString)
72                 {
73                         connectionTimeout = 15;
74                         ConnectionString = connectionString;
75                 }
76
77                 #endregion // Constructors
78
79                 #region Properties
80
81                 internal IntPtr hDbc {
82                         get { return hdbc; }
83                 }
84
85                 [OdbcCategoryAttribute ("DataCategory_Data")]
86                 [DefaultValue ("")]
87                 [OdbcDescriptionAttribute ("Information used to connect to a Data Source")]
88                 [RefreshPropertiesAttribute (RefreshProperties.All)]
89                 [EditorAttribute ("Microsoft.VSDesigner.Data.Odbc.Design.OdbcConnectionStringEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
90                 [RecommendedAsConfigurableAttribute (true)]
91                 public
92 #if NET_2_0
93                 override
94 #endif
95                 string ConnectionString {
96                         get {
97                                 if (connectionString == null)
98                                         return string.Empty;
99                                 return connectionString;
100                         }
101                         set { connectionString = value; }
102                 }
103                 
104                 [OdbcDescriptionAttribute ("Current connection timeout value, not settable  in the ConnectionString")]
105                 [DefaultValue (15)]
106 #if NET_2_0
107                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
108 #endif
109                 public
110 #if NET_2_0
111                 new
112 #endif // NET_2_0
113                 int ConnectionTimeout {
114                         get {
115                                 return connectionTimeout;
116                         }
117                         set {
118                                 if (value < 0)
119                                         throw new ArgumentException("Timout should not be less than zero.");
120                                 connectionTimeout = value;
121                         }
122                 }
123
124                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
125                 [OdbcDescriptionAttribute ("Current data source Catlog value, 'Database=X' in the ConnectionString")]
126                 public
127 #if NET_2_0
128                 override
129 #endif // NET_2_0
130                 string Database {
131                         get {
132                                 if (State == ConnectionState.Closed)
133                                         return string.Empty;
134                                 return GetInfo (OdbcInfo.DatabaseName);
135                         }
136                 }
137
138                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
139                 [OdbcDescriptionAttribute ("The ConnectionState indicating whether the connection is open or closed")]
140                 [BrowsableAttribute (false)]
141                 public
142 #if NET_2_0
143                 override
144 #endif // NET_2_0
145                 ConnectionState State {
146                         get {
147                                 if (hdbc != IntPtr.Zero)
148                                         return ConnectionState.Open;
149                                 return ConnectionState.Closed;
150                         }
151                 }
152
153                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
154                 [OdbcDescriptionAttribute ("Current data source, 'Server=X' in the ConnectionString")]
155 #if NET_2_0
156                 [Browsable (false)]
157 #endif
158                 public
159 #if NET_2_0
160                 override
161 #endif // NET_2_0
162                 string DataSource {
163                         get {
164                                 if (State == ConnectionState.Closed)
165                                         return string.Empty;
166                                 return GetInfo (OdbcInfo.DataSourceName);
167                         }
168                 }
169
170 #if NET_2_0
171                 [Browsable (false)]
172 #endif
173                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
174                 [OdbcDescriptionAttribute ("Current ODBC Driver")]
175                 public string Driver {
176                         get {
177                                 if (State == ConnectionState.Closed)
178                                         return string.Empty;
179
180                                 return GetInfo (OdbcInfo.DriverName);
181                         }
182                 }
183                 
184                 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
185                 [OdbcDescriptionAttribute ("Version of the product accessed by the ODBC Driver")]
186                 [BrowsableAttribute (false)]
187                 public
188 #if NET_2_0
189                 override
190 #endif // NET_2_0
191                 string ServerVersion {
192                         get {
193                                 return GetInfo (OdbcInfo.DbmsVersion);
194                         }
195                 }
196
197                 internal string SafeDriver {
198                         get {
199                                 string driver_name = GetSafeInfo (OdbcInfo.DriverName);
200                                 if (driver_name == null)
201                                         return string.Empty;
202                                 return driver_name;
203                         }
204                 }
205
206                 #endregion // Properties
207         
208                 #region Methods
209         
210                 public
211 #if NET_2_0
212                 new
213 #endif // NET_2_0
214                 OdbcTransaction BeginTransaction ()
215                 {
216                         return BeginTransaction (IsolationLevel.Unspecified);
217                 }
218
219 #if ONLY_1_1
220                 IDbTransaction IDbConnection.BeginTransaction ()
221                 {
222                         return (IDbTransaction) BeginTransaction ();
223                 }
224 #endif // ONLY_1_1
225
226 #if NET_2_0
227                 protected override DbTransaction BeginDbTransaction (IsolationLevel isolationLevel)
228                 {
229                         return BeginTransaction (isolationLevel);
230                 }
231 #endif
232
233                 public
234 #if NET_2_0
235                 new
236 #endif // NET_2_0
237                 OdbcTransaction BeginTransaction (IsolationLevel isolevel)
238                 {
239                         if (State == ConnectionState.Closed)
240                                 throw ExceptionHelper.ConnectionClosed ();
241
242                         if (transaction == null) {
243                                 transaction = new OdbcTransaction (this, isolevel);
244                                 return transaction;
245                         } else
246                                 throw new InvalidOperationException ();
247                 }
248
249 #if ONLY_1_1
250                 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel isolevel)
251                 {
252                         return (IDbTransaction) BeginTransaction (isolevel);
253                 }
254 #endif // ONLY_1_1
255
256                 public
257 #if NET_2_0
258                 override
259 #endif // NET_2_0
260                 void Close ()
261                 {
262                         OdbcReturn ret = OdbcReturn.Error;
263                         if (State == ConnectionState.Open) {
264                                 // close any associated commands
265                                 if (linkedCommands != null) {
266                                         for (int i = 0; i < linkedCommands.Count; i++) {
267                                                 WeakReference wr = (WeakReference) linkedCommands [i];
268                                                 if (wr == null)
269                                                         continue;
270                                                 OdbcCommand c = (OdbcCommand) wr.Target;
271                                                 if (c != null)
272                                                         c.Unlink ();
273                                         }
274                                         linkedCommands = null;
275                                 }
276
277                                 // disconnect
278                                 ret = libodbc.SQLDisconnect (hdbc);
279                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
280                                         throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
281
282                                 FreeHandles ();
283                                 transaction = null;
284                                 RaiseStateChange (ConnectionState.Open, ConnectionState.Closed);
285                         }
286                 }
287
288                 public
289 #if NET_2_0
290                 new
291 #endif // NET_2_0
292                 OdbcCommand CreateCommand ()
293                 {
294                         return new OdbcCommand (string.Empty, this, transaction);
295                 }
296
297                 public
298 #if NET_2_0
299                 override
300 #endif // NET_2_0
301                 void ChangeDatabase (string value)
302                 {
303                         IntPtr ptr = IntPtr.Zero;
304                         OdbcReturn ret = OdbcReturn.Error;
305
306                         try {
307                                 ptr = Marshal.StringToHGlobalUni (value);
308                                 ret = libodbc.SQLSetConnectAttr (hdbc, OdbcConnectionAttribute.CurrentCatalog, ptr, value.Length * 2);
309
310                                 if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo)
311                                         throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
312                         } finally {
313                                 if (ptr != IntPtr.Zero)
314                                         Marshal.FreeCoTaskMem (ptr);
315                         }
316                 }
317
318                 protected override void Dispose (bool disposing)
319                 {
320                         if (!this.disposed) {
321                                 try {
322                                         // release the native unmananged resources
323                                         this.Close ();
324                                         this.disposed = true;
325                                 } finally {
326                                         // call Dispose on the base class
327                                         base.Dispose (disposing);
328                                 }
329                         }
330                 }
331
332                 [MonoTODO]
333                 object ICloneable.Clone ()
334                 {
335                         throw new NotImplementedException ();
336                 }
337
338 #if ONLY_1_1
339                 IDbCommand IDbConnection.CreateCommand ()
340                 {
341                         return (IDbCommand) CreateCommand ();
342                 }
343 #endif //ONLY_1_1
344
345 #if NET_2_0
346                 protected override DbCommand CreateDbCommand ()
347                 {
348                         return CreateCommand ();
349                 }
350 #endif
351
352                 public
353 #if NET_2_0
354                 override
355 #endif // NET_2_0
356                 void Open ()
357                 {
358                         if (State == ConnectionState.Open)
359                                 throw new InvalidOperationException ();
360
361                         OdbcReturn ret = OdbcReturn.Error;
362                         OdbcException e = null;
363                 
364                         try {
365                                 // allocate Environment handle
366                                 ret = libodbc.SQLAllocHandle (OdbcHandleType.Env, IntPtr.Zero, ref henv);
367                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) {
368                                         OdbcErrorCollection errors = new OdbcErrorCollection ();
369                                         errors.Add (new OdbcError (this));
370                                         e = new OdbcException (errors);
371                                         MessageHandler (e);
372                                         throw e;
373                                 }
374
375                                 ret = libodbc.SQLSetEnvAttr (henv, OdbcEnv.OdbcVersion, (IntPtr) libodbc.SQL_OV_ODBC3 , 0); 
376                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
377                                         throw CreateOdbcException (OdbcHandleType.Env, henv);
378
379                                 // allocate connection handle
380                                 ret = libodbc.SQLAllocHandle (OdbcHandleType.Dbc, henv, ref hdbc);
381                                 if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
382                                         throw CreateOdbcException (OdbcHandleType.Env, henv);
383
384                                 // DSN connection
385                                 if (ConnectionString.ToLower ().IndexOf ("dsn=") >= 0) {
386                                         string _uid = string.Empty, _pwd = string.Empty, _dsn = string.Empty;
387                                         string [] items = ConnectionString.Split (new char[1] {';'});
388                                         foreach (string item in items)
389                                         {
390                                                 string [] parts = item.Split (new char[1] {'='});
391                                                 switch (parts [0].Trim ().ToLower ()) {
392                                                 case "dsn":
393                                                         _dsn = parts [1].Trim ();
394                                                         break;
395                                                 case "uid":
396                                                         _uid = parts [1].Trim ();
397                                                         break;
398                                                 case "pwd":
399                                                         _pwd = parts [1].Trim ();
400                                                         break;
401                                                 }
402                                         }
403                                         ret = libodbc.SQLConnect(hdbc, _dsn, -3, _uid, -3, _pwd, -3);
404                                         if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
405                                                 throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
406                                 } else {
407                                         // DSN-less Connection
408                                         string OutConnectionString = new String (' ',1024);
409                                         short OutLen = 0;
410                                         ret = libodbc.SQLDriverConnect (hdbc, IntPtr.Zero, ConnectionString, -3, 
411                                                 OutConnectionString, (short) OutConnectionString.Length, ref OutLen, 0);
412                                         if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
413                                                 throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
414                                 }
415
416                                 RaiseStateChange (ConnectionState.Closed, ConnectionState.Open);
417                         } catch {
418                                 // free handles if any.
419                                 FreeHandles ();
420                                 throw;
421                         }
422                         disposed = false;
423                 }
424
425                 [MonoTODO]
426                 public static void ReleaseObjectPool ()
427                 {
428                         throw new NotImplementedException ();
429                 }
430
431                 private void FreeHandles ()
432                 {
433                         OdbcReturn ret = OdbcReturn.Error;
434                         if (hdbc != IntPtr.Zero) {
435                                 ret = libodbc.SQLFreeHandle ((ushort) OdbcHandleType.Dbc, hdbc);
436                                 if ( (ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
437                                         throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
438                         }
439                         hdbc = IntPtr.Zero;
440
441                         if (henv != IntPtr.Zero) {
442                                 ret = libodbc.SQLFreeHandle ((ushort) OdbcHandleType.Env, henv);
443                                 if ( (ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo))
444                                         throw CreateOdbcException (OdbcHandleType.Env, henv);
445                         }
446                         henv = IntPtr.Zero;
447                 }
448
449 #if NET_2_0
450                 public override DataTable GetSchema ()
451                 {
452                         if (State == ConnectionState.Closed)
453                                 throw ExceptionHelper.ConnectionClosed ();
454                         return MetaDataCollections.Instance;
455                 }
456
457                 public override DataTable GetSchema (string collectionName)
458                 {
459                         return GetSchema (collectionName, null);
460                 }
461
462                 public override DataTable GetSchema (string collectionName, string [] restrictionValues)
463                 {
464                         if (State == ConnectionState.Closed)
465                                 throw ExceptionHelper.ConnectionClosed ();
466                         return GetSchema (collectionName, null);
467                 }
468
469                 [MonoTODO]
470                 public override void EnlistTransaction (Transaction transaction)
471                 {
472                         throw new NotImplementedException ();
473                 }
474 #endif
475
476                 [MonoTODO]
477                 public void EnlistDistributedTransaction (ITransaction transaction)
478                 {
479                         throw new NotImplementedException ();
480                 }
481
482                 internal string GetInfo (OdbcInfo info)
483                 {
484                         if (State == ConnectionState.Closed)
485                                 throw new InvalidOperationException ("The connection is closed.");
486
487                         OdbcReturn ret = OdbcReturn.Error;
488                         short max_length = 512;
489                         byte [] buffer = new byte [512];
490                         short actualLength = 0;
491
492                         ret = libodbc.SQLGetInfo (hdbc, info, buffer, max_length, ref actualLength);
493                         if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo)
494                                 throw CreateOdbcException (OdbcHandleType.Dbc, hdbc);
495                         return Encoding.Unicode.GetString (buffer, 0, actualLength);
496                 }
497
498                 string GetSafeInfo (OdbcInfo info)
499                 {
500                         if (State == ConnectionState.Closed)
501                                 return null;
502
503                         OdbcReturn ret = OdbcReturn.Error;
504                         short max_length = 512;
505                         byte [] buffer = new byte [512];
506                         short actualLength = 0;
507
508                         ret = libodbc.SQLGetInfo (hdbc, info, buffer, max_length, ref actualLength);
509                         if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo)
510                                 return null;
511                         return Encoding.Unicode.GetString (buffer, 0, actualLength);
512                 }
513
514                 private void RaiseStateChange (ConnectionState from, ConnectionState to)
515                 {
516 #if ONLY_1_1
517                         if (StateChange != null)
518                                 StateChange (this, new StateChangeEventArgs (from, to));
519 #else
520                         base.OnStateChange (new StateChangeEventArgs (from, to));
521 #endif
522                 }
523
524                 private OdbcInfoMessageEventArgs CreateOdbcInfoMessageEvent (OdbcErrorCollection errors)
525                 {
526                         return new OdbcInfoMessageEventArgs (errors);
527                 }
528
529                 private void OnOdbcInfoMessage (OdbcInfoMessageEventArgs e)
530                 {
531                         if (InfoMessage != null)
532                                 InfoMessage (this, e);
533                 }
534
535                 internal OdbcException CreateOdbcException (OdbcHandleType HandleType, IntPtr Handle)
536                 {
537                         short buflen = 256;
538                         short txtlen = 0;
539                         int nativeerror = 0;
540                         OdbcReturn ret = OdbcReturn.Success;
541
542                         OdbcErrorCollection errors = new OdbcErrorCollection ();
543
544                         while (true) {
545                                 byte [] buf_MsgText = new byte [buflen * 2];
546                                 byte [] buf_SqlState = new byte [buflen * 2];
547
548                                 switch (HandleType) {
549                                 case OdbcHandleType.Dbc:
550                                         ret = libodbc.SQLError (IntPtr.Zero, Handle, IntPtr.Zero, buf_SqlState,
551                                                 ref nativeerror, buf_MsgText, buflen, ref txtlen);
552                                         break;
553                                 case OdbcHandleType.Stmt:
554                                         ret = libodbc.SQLError (IntPtr.Zero, IntPtr.Zero, Handle, buf_SqlState,
555                                                 ref nativeerror, buf_MsgText, buflen, ref txtlen);
556                                         break;
557                                 case OdbcHandleType.Env:
558                                         ret = libodbc.SQLError (Handle, IntPtr.Zero, IntPtr.Zero, buf_SqlState,
559                                                 ref nativeerror, buf_MsgText, buflen, ref txtlen);
560                                         break;
561                                 }
562
563                                 if (ret != OdbcReturn.Success)
564                                         break;
565
566                                 string state = RemoveTrailingNullChar (Encoding.Unicode.GetString (buf_SqlState));
567                                 string message = Encoding.Unicode.GetString (buf_MsgText, 0, txtlen * 2);
568
569                                 errors.Add (new OdbcError (message, state, nativeerror));
570                         }
571
572                         string source = SafeDriver;
573                         foreach (OdbcError error in errors)
574                                 error.SetSource (source);
575                         return new OdbcException (errors);
576                 }
577
578                 static string RemoveTrailingNullChar (string value)
579                 {
580                         return value.TrimEnd ('\0');
581                 }
582
583                 internal void Link (OdbcCommand cmd)
584                 {
585                         if (linkedCommands == null)
586                                 linkedCommands = new ArrayList ();
587                         linkedCommands.Add (new WeakReference (cmd));
588                 }
589
590                 internal void Unlink (OdbcCommand cmd)
591                 {
592                         if (linkedCommands == null)
593                                 return;
594
595                         for (int i = 0; i < linkedCommands.Count; i++) {
596                                 WeakReference wr = (WeakReference) linkedCommands [i];
597                                 if (wr == null)
598                                         continue;
599                                 OdbcCommand c = (OdbcCommand) wr.Target;
600                                 if (c == cmd) {
601                                         linkedCommands [i] = null;
602                                         break;
603                                 }
604                         }
605                 }
606
607                 #endregion
608
609                 #region Events and Delegates
610
611 #if ONLY_1_1
612                 [OdbcDescription ("DbConnection_StateChange")]
613                 [OdbcCategory ("DataCategory_StateChange")]
614                 public event StateChangeEventHandler StateChange;
615 #endif // ONLY_1_1
616
617                 [OdbcDescription ("DbConnection_InfoMessage")]
618                 [OdbcCategory ("DataCategory_InfoMessage")]
619                 public event OdbcInfoMessageEventHandler InfoMessage;
620
621                 private void MessageHandler (OdbcException e)
622                 {
623                         OnOdbcInfoMessage (CreateOdbcInfoMessageEvent (e.Errors));
624                 }
625
626                 #endregion
627         }
628 }