OdbcConnection.cs 16 KB

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