SqlConnection.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. //
  2. // System.Data.SqlClient.SqlConnection.cs
  3. //
  4. // Author:
  5. // Rodrigo Moya ([email protected])
  6. // Daniel Morgan ([email protected])
  7. //
  8. // (C) Ximian, Inc 2002
  9. // (C) Daniel Morgan 2002
  10. //
  11. // Credits:
  12. // SQL and concepts were used from libgda 0.8.190 (GNOME Data Access)
  13. // http://www.gnome-db.org/
  14. // with permission from the authors of the
  15. // PostgreSQL provider in libgda:
  16. // Michael Lausch <[email protected]>
  17. // Rodrigo Moya <[email protected]>
  18. // Vivien Malerba <[email protected]>
  19. // Gonzalo Paniagua Javier <[email protected]>
  20. //
  21. // use #define DEBUG_SqlConnection if you want to spew debug messages
  22. // #define DEBUG_SqlConnection
  23. using System;
  24. using System.Collections;
  25. using System.ComponentModel;
  26. using System.Data;
  27. using System.Data.Common;
  28. using System.Runtime.InteropServices;
  29. using System.Text;
  30. namespace System.Data.SqlClient {
  31. /// <summary>
  32. /// Represents an open connection to a SQL data source
  33. /// </summary>
  34. //public sealed class SqlConnection : Component, IDbConnection,
  35. // ICloneable
  36. public sealed class SqlConnection : IDbConnection, IDisposable
  37. {
  38. // FIXME: Need to implement class Component,
  39. // and interfaces: ICloneable and IDisposable
  40. #region Fields
  41. private PostgresTypes types = null;
  42. private IntPtr pgConn = IntPtr.Zero;
  43. // PGConn (Postgres Connection)
  44. private string connectionString = "";
  45. // OLE DB Connection String
  46. private string pgConnectionString = "";
  47. // PostgreSQL Connection String
  48. private SqlTransaction trans = null;
  49. private int connectionTimeout = 15;
  50. // default for 15 seconds
  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. // connection state
  77. private ConnectionState conState = ConnectionState.Closed;
  78. // DataReader state
  79. private SqlDataReader rdr = null;
  80. private bool dataReaderOpen = false;
  81. // FIXME: if true, throw an exception if SqlConnection
  82. // is used for anything other than reading
  83. // data using SqlDataReader
  84. private string versionString = "Unknown";
  85. #endregion // Fields
  86. #region Constructors
  87. // A lot of the defaults were initialized in the Fields
  88. [MonoTODO]
  89. public SqlConnection () {
  90. }
  91. [MonoTODO]
  92. public SqlConnection (String connectionString) {
  93. SetConnectionString (connectionString);
  94. }
  95. #endregion // Constructors
  96. #region Destructors
  97. [MonoTODO]
  98. public void Dispose () {
  99. // FIXME: release resources properly
  100. Close ();
  101. // Dispose (true);
  102. }
  103. // aka Finalize
  104. // [ClassInterface(ClassInterfaceType.AutoDual)]
  105. [MonoTODO]
  106. ~SqlConnection() {
  107. // FIXME: this class need
  108. // a destructor to release resources
  109. // Also, take a look at Dispose
  110. // Dispose (false);
  111. }
  112. #endregion // Destructors
  113. #region Public Methods
  114. IDbTransaction IDbConnection.BeginTransaction () {
  115. return BeginTransaction ();
  116. }
  117. public SqlTransaction BeginTransaction () {
  118. return TransactionBegin (); // call private method
  119. }
  120. IDbTransaction IDbConnection.BeginTransaction (IsolationLevel
  121. il) {
  122. return BeginTransaction (il);
  123. }
  124. public SqlTransaction BeginTransaction (IsolationLevel il) {
  125. return TransactionBegin (il); // call private method
  126. }
  127. // PostgreSQL does not support named transactions/savepoint
  128. // nor nested transactions
  129. [Obsolete]
  130. public SqlTransaction BeginTransaction(string transactionName) {
  131. return TransactionBegin (); // call private method
  132. }
  133. [Obsolete]
  134. public SqlTransaction BeginTransaction(IsolationLevel iso,
  135. string transactionName) {
  136. return TransactionBegin (iso); // call private method
  137. }
  138. [MonoTODO]
  139. public void ChangeDatabase (string databaseName) {
  140. throw new NotImplementedException ();
  141. }
  142. [MonoTODO]
  143. public void Close () {
  144. if(dataReaderOpen == true) {
  145. // TODO: what do I do if
  146. // the user Closes the connection
  147. // without closing the Reader first?
  148. }
  149. CloseDataSource ();
  150. }
  151. IDbCommand IDbConnection.CreateCommand () {
  152. return CreateCommand ();
  153. }
  154. public SqlCommand CreateCommand () {
  155. SqlCommand sqlcmd = new SqlCommand ("", this);
  156. return sqlcmd;
  157. }
  158. [MonoTODO]
  159. public void Open () {
  160. if(dbname.Equals(""))
  161. throw new InvalidOperationException(
  162. "dbname missing");
  163. else if(conState == ConnectionState.Open)
  164. throw new InvalidOperationException(
  165. "ConnnectionState is already Open");
  166. ConnStatusType connStatus;
  167. // FIXME: check to make sure we have
  168. // everything to connect,
  169. // otherwise, throw an exception
  170. pgConn = PostgresLibrary.PQconnectdb
  171. (pgConnectionString);
  172. // FIXME: should we use PQconnectStart/PQconnectPoll
  173. // instead of PQconnectdb?
  174. // PQconnectdb blocks
  175. // PQconnectStart/PQconnectPoll is non-blocking
  176. connStatus = PostgresLibrary.PQstatus (pgConn);
  177. if(connStatus == ConnStatusType.CONNECTION_OK) {
  178. // Successfully Connected
  179. SetupConnection();
  180. }
  181. else {
  182. String errorMessage = PostgresLibrary.
  183. PQerrorMessage (pgConn);
  184. errorMessage += ": Could not connect to database.";
  185. throw new SqlException(0, 0,
  186. errorMessage, 0, "",
  187. host, "SqlConnection", 0);
  188. }
  189. }
  190. #endregion // Public Methods
  191. #region Protected Methods
  192. // FIXME: protected override void Dispose overrides Component
  193. // however, including Component causes other problems
  194. /*
  195. [MonoTODO]
  196. protected override void Dispose (bool disposing)
  197. {
  198. throw new NotImplementedException ();
  199. }
  200. */
  201. #endregion
  202. #region Internal Methods
  203. // Used to prevent SqlConnection
  204. // from doing anything while
  205. // SqlDataReader is open.
  206. // Open the Reader. (called from SqlCommand)
  207. internal void OpenReader(SqlDataReader reader)
  208. {
  209. if(dataReaderOpen == true) {
  210. // TODO: throw exception here?
  211. // because a reader
  212. // is already open
  213. }
  214. else {
  215. rdr = reader;
  216. dataReaderOpen = true;
  217. }
  218. }
  219. // Used to prevent SqlConnection
  220. // from doing anything while
  221. // SqlDataReader is open
  222. // Close the Reader (called from SqlCommand)
  223. // if closeConnection true, Close() the connection
  224. // this is based on CommandBehavior.CloseConnection
  225. internal void CloseReader(bool closeConnection)
  226. { if(closeConnection == true)
  227. CloseDataSource();
  228. else
  229. dataReaderOpen = false;
  230. }
  231. #endregion // Internal Methods
  232. #region Private Methods
  233. private void SetupConnection() {
  234. conState = ConnectionState.Open;
  235. // FIXME: load types into hashtable
  236. types = new PostgresTypes(this);
  237. types.Load();
  238. versionString = GetDatabaseServerVersion();
  239. // set DATE style to YYYY/MM/DD
  240. IntPtr pgResult = IntPtr.Zero;
  241. pgResult = PostgresLibrary.PQexec (pgConn, "SET DATESTYLE TO 'ISO'");
  242. PostgresLibrary.PQclear (pgResult);
  243. pgResult = IntPtr.Zero;
  244. }
  245. private string GetDatabaseServerVersion()
  246. {
  247. SqlCommand cmd = new SqlCommand("select version()",this);
  248. return (string) cmd.ExecuteScalar();
  249. }
  250. private void CloseDataSource () {
  251. // FIXME: just a quick hack
  252. if(conState == ConnectionState.Open) {
  253. if(trans != null)
  254. if(trans.DoingTransaction == true) {
  255. trans.Rollback();
  256. // trans.Dispose();
  257. trans = null;
  258. }
  259. conState = ConnectionState.Closed;
  260. PostgresLibrary.PQfinish (pgConn);
  261. pgConn = IntPtr.Zero;
  262. }
  263. }
  264. private void SetConnectionString (string connectionString) {
  265. // FIXME: perform error checking on string
  266. // while translating string from
  267. // OLE DB format to PostgreSQL
  268. // connection string format
  269. //
  270. // OLE DB: "host=localhost;dbname=test;user=joe;password=smoe"
  271. // PostgreSQL: "host=localhost dbname=test user=joe password=smoe"
  272. //
  273. // For OLE DB, you would have the additional
  274. // "provider=postgresql"
  275. // OleDbConnection you would be using libgda, maybe
  276. // it would be
  277. // "provider=OAFIID:GNOME_Database_Postgres_Provider"
  278. // instead.
  279. //
  280. // Also, parse the connection string into properties
  281. // FIXME: if connection is open, you can
  282. // not set the connection
  283. // string, throw an exception
  284. this.connectionString = connectionString;
  285. pgConnectionString = ConvertStringToPostgres (
  286. connectionString);
  287. #if DEBUG_SqlConnection
  288. Console.WriteLine(
  289. "OLE-DB Connection String [in]: " +
  290. this.ConnectionString);
  291. Console.WriteLine(
  292. "Postgres Connection String [out]: " +
  293. pgConnectionString);
  294. #endif // DEBUG_SqlConnection
  295. }
  296. private String ConvertStringToPostgres (String
  297. oleDbConnectionString) {
  298. StringBuilder postgresConnection =
  299. new StringBuilder();
  300. string result;
  301. string[] connectionParameters;
  302. char[] semicolon = new Char[1];
  303. semicolon[0] = ';';
  304. // FIXME: what is the max number of value pairs
  305. // can there be for the OLE DB
  306. // connnection string? what about libgda max?
  307. // what about postgres max?
  308. // FIXME: currently assuming value pairs are like:
  309. // "key1=value1;key2=value2;key3=value3"
  310. // Need to deal with values that have
  311. // single or double quotes. And error
  312. // handling of that too.
  313. // "key1=value1;key2='value2';key=\"value3\""
  314. // FIXME: put the connection parameters
  315. // from the connection
  316. // string into a
  317. // Hashtable (System.Collections)
  318. // instead of using private variables
  319. // to store them
  320. connectionParameters = oleDbConnectionString.
  321. Split (semicolon);
  322. foreach (string sParameter in connectionParameters) {
  323. if(sParameter.Length > 0) {
  324. BreakConnectionParameter (sParameter);
  325. postgresConnection.
  326. Append (sParameter +
  327. " ");
  328. }
  329. }
  330. result = postgresConnection.ToString ();
  331. return result;
  332. }
  333. private bool BreakConnectionParameter (String sParameter) {
  334. bool addParm = true;
  335. int index;
  336. index = sParameter.IndexOf ("=");
  337. if (index > 0) {
  338. string parmKey, parmValue;
  339. // separate string "key=value" to
  340. // string "key" and "value"
  341. parmKey = sParameter.Substring (0, index);
  342. parmValue = sParameter.Substring (index + 1,
  343. sParameter.Length - index - 1);
  344. switch(parmKey.ToLower()) {
  345. case "hostaddr":
  346. hostaddr = parmValue;
  347. break;
  348. case "port":
  349. port = parmValue;
  350. break;
  351. case "host":
  352. // set DataSource property
  353. host = parmValue;
  354. break;
  355. case "dbname":
  356. // set Database property
  357. dbname = parmValue;
  358. break;
  359. case "user":
  360. user = parmValue;
  361. break;
  362. case "password":
  363. password = parmValue;
  364. // addParm = false;
  365. break;
  366. case "options":
  367. options = parmValue;
  368. break;
  369. case "tty":
  370. tty = parmValue;
  371. break;
  372. case "requiressl":
  373. requiressl = parmValue;
  374. break;
  375. }
  376. }
  377. return addParm;
  378. }
  379. private SqlTransaction TransactionBegin () {
  380. // FIXME: need to keep track of
  381. // transaction in-progress
  382. trans = new SqlTransaction ();
  383. // using internal methods of SqlTransaction
  384. trans.SetConnection (this);
  385. trans.Begin();
  386. return trans;
  387. }
  388. private SqlTransaction TransactionBegin (IsolationLevel il) {
  389. // FIXME: need to keep track of
  390. // transaction in-progress
  391. TransactionBegin();
  392. trans.SetIsolationLevel (il);
  393. return trans;
  394. }
  395. #endregion
  396. #region Public Properties
  397. [MonoTODO]
  398. public ConnectionState State {
  399. get {
  400. return conState;
  401. }
  402. }
  403. public string ConnectionString {
  404. get {
  405. return connectionString;
  406. }
  407. set {
  408. SetConnectionString (value);
  409. }
  410. }
  411. public int ConnectionTimeout {
  412. get {
  413. return connectionTimeout;
  414. }
  415. }
  416. public string Database {
  417. get {
  418. return dbname;
  419. }
  420. }
  421. public string DataSource {
  422. get {
  423. return host;
  424. }
  425. }
  426. /*
  427. * FIXME: this is here because of Component?
  428. [MonoTODO]
  429. protected bool DesignMode {
  430. get {
  431. throw new NotImplementedException ();
  432. }
  433. }
  434. */
  435. public int PacketSize {
  436. get {
  437. throw new NotImplementedException ();
  438. }
  439. }
  440. public string ServerVersion {
  441. get {
  442. return versionString;
  443. }
  444. }
  445. #endregion // Public Properties
  446. #region Internal Properties
  447. // For System.Data.SqlClient classes
  448. // to get the current transaction
  449. // in progress - if any
  450. internal SqlTransaction Transaction {
  451. get {
  452. return trans;
  453. }
  454. }
  455. // For System.Data.SqlClient classes
  456. // to get the unmanaged PostgreSQL connection
  457. internal IntPtr PostgresConnection {
  458. get {
  459. return pgConn;
  460. }
  461. }
  462. // For System.Data.SqlClient classes
  463. // to get the list PostgreSQL types
  464. // so can look up based on OID to
  465. // get the .NET System type.
  466. internal ArrayList Types {
  467. get {
  468. return types.List;
  469. }
  470. }
  471. // Used to prevent SqlConnection
  472. // from doing anything while
  473. // SqlDataReader is open
  474. internal bool IsReaderOpen {
  475. get {
  476. return dataReaderOpen;
  477. }
  478. }
  479. #endregion // Internal Properties
  480. #region Events
  481. public event
  482. SqlInfoMessageEventHandler InfoMessage;
  483. public event
  484. StateChangeEventHandler StateChange;
  485. #endregion
  486. #region Inner Classes
  487. private class PostgresTypes {
  488. // TODO: create hashtable for
  489. // PostgreSQL types to .NET types
  490. // containing: oid, typname, SqlDbType
  491. private Hashtable hashTypes;
  492. private ArrayList pgTypes;
  493. private SqlConnection con;
  494. // Got this SQL with the permission from
  495. // the authors of libgda
  496. private const string SEL_SQL_GetTypes =
  497. "SELECT oid, typname FROM pg_type " +
  498. "WHERE typrelid = 0 AND typname !~ '^_' " +
  499. " AND typname not in ('SET', 'cid', " +
  500. "'int2vector', 'oidvector', 'regproc', " +
  501. "'smgr', 'tid', 'unknown', 'xid') " +
  502. "ORDER BY typname";
  503. internal PostgresTypes(SqlConnection sqlcon) {
  504. con = sqlcon;
  505. hashTypes = new Hashtable();
  506. }
  507. private void AddPgType(Hashtable types,
  508. string typname, DbType dbType) {
  509. PostgresType pgType = new PostgresType();
  510. pgType.typname = typname;
  511. pgType.dbType = dbType;
  512. types.Add(pgType.typname, pgType);
  513. }
  514. private void BuildTypes(IntPtr pgResult,
  515. int nRows, int nFields) {
  516. String value;
  517. int r;
  518. for(r = 0; r < nRows; r++) {
  519. PostgresType pgType =
  520. new PostgresType();
  521. // get data value (oid)
  522. value = PostgresLibrary.
  523. PQgetvalue(
  524. pgResult,
  525. r, 0);
  526. pgType.oid = Int32.Parse(value);
  527. // get data value (typname)
  528. value = PostgresLibrary.
  529. PQgetvalue(
  530. pgResult,
  531. r, 1);
  532. pgType.typname = String.Copy(value);
  533. pgType.dbType = PostgresHelper.
  534. TypnameToSqlDbType(
  535. pgType.typname);
  536. pgTypes.Add(pgType);
  537. }
  538. pgTypes = ArrayList.ReadOnly(pgTypes);
  539. }
  540. internal void Load() {
  541. pgTypes = new ArrayList();
  542. IntPtr pgResult = IntPtr.Zero; // PGresult
  543. if(con.State != ConnectionState.Open)
  544. throw new InvalidOperationException(
  545. "ConnnectionState is not Open");
  546. // FIXME: PQexec blocks
  547. // while PQsendQuery is non-blocking
  548. // which is better to use?
  549. // int PQsendQuery(PGconn *conn,
  550. // const char *query);
  551. // execute SQL command
  552. // uses internal property to get the PGConn IntPtr
  553. pgResult = PostgresLibrary.
  554. PQexec (con.PostgresConnection, SEL_SQL_GetTypes);
  555. if(pgResult.Equals(IntPtr.Zero)) {
  556. throw new SqlException(0, 0,
  557. "No Resultset from PostgreSQL", 0, "",
  558. con.DataSource, "SqlConnection", 0);
  559. }
  560. else {
  561. ExecStatusType execStatus;
  562. execStatus = PostgresLibrary.
  563. PQresultStatus (pgResult);
  564. if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
  565. int nRows;
  566. int nFields;
  567. nRows = PostgresLibrary.
  568. PQntuples(pgResult);
  569. nFields = PostgresLibrary.
  570. PQnfields(pgResult);
  571. BuildTypes (pgResult, nRows, nFields);
  572. // close result set
  573. PostgresLibrary.PQclear (pgResult);
  574. pgResult = IntPtr.Zero;
  575. }
  576. else {
  577. String errorMessage;
  578. errorMessage = PostgresLibrary.
  579. PQresStatus(execStatus);
  580. errorMessage += " " + PostgresLibrary.
  581. PQresultErrorMessage(pgResult);
  582. // close result set
  583. PostgresLibrary.PQclear (pgResult);
  584. pgResult = IntPtr.Zero;
  585. throw new SqlException(0, 0,
  586. errorMessage, 0, "",
  587. con.DataSource, "SqlConnection", 0);
  588. }
  589. }
  590. }
  591. public ArrayList List {
  592. get {
  593. return pgTypes;
  594. }
  595. }
  596. }
  597. #endregion
  598. }
  599. }