SqlConnection.cs 16 KB

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