SqlConnection.cs 16 KB

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