SqlConnection.cs 17 KB

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