SqlConnection.cs 16 KB

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