SqlCommand.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. //
  2. // System.Data.SqlClient.SqlCommand.cs
  3. //
  4. // Author:
  5. // Rodrigo Moya ([email protected])
  6. // Daniel Morgan ([email protected])
  7. //
  8. // (C) Ximian, Inc 2002 http://www.ximian.com/
  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_SqlCommand if you want to spew debug messages
  22. // #define DEBUG_SqlCommand
  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. using System.Xml;
  31. namespace System.Data.SqlClient {
  32. /// <summary>
  33. /// Represents a SQL statement that is executed
  34. /// while connected to a SQL database.
  35. /// </summary>
  36. // public sealed class SqlCommand : Component, IDbCommand, ICloneable
  37. public sealed class SqlCommand : IDbCommand {
  38. // FIXME: Console.WriteLine() is used for debugging throughout
  39. #region Fields
  40. private string sql = "";
  41. private int timeout = 30;
  42. // default is 30 seconds
  43. // for command execution
  44. private SqlConnection conn = null;
  45. private SqlTransaction trans = null;
  46. private CommandType cmdType = CommandType.Text;
  47. private bool designTime = false;
  48. private SqlParameterCollection parmCollection = new
  49. SqlParameterCollection();
  50. // SqlDataReader state data for ExecuteReader()
  51. private SqlDataReader dataReader;
  52. private string[] queries;
  53. private int currentQuery;
  54. CommandBehavior cmdBehavior;
  55. #endregion // Fields
  56. #region Constructors
  57. public SqlCommand() {
  58. sql = "";
  59. }
  60. public SqlCommand (string cmdText) {
  61. sql = cmdText;
  62. }
  63. public SqlCommand (string cmdText, SqlConnection connection) {
  64. sql = cmdText;
  65. conn = connection;
  66. }
  67. public SqlCommand (string cmdText, SqlConnection connection,
  68. SqlTransaction transaction) {
  69. sql = cmdText;
  70. conn = connection;
  71. trans = transaction;
  72. }
  73. #endregion // Constructors
  74. #region Methods
  75. [MonoTODO]
  76. public void Cancel () {
  77. // FIXME: use non-blocking Exec for this
  78. throw new NotImplementedException ();
  79. }
  80. // FIXME: is this the correct way to return a stronger type?
  81. [MonoTODO]
  82. IDbDataParameter IDbCommand.CreateParameter () {
  83. return CreateParameter ();
  84. }
  85. [MonoTODO]
  86. public SqlParameter CreateParameter () {
  87. return new SqlParameter ();
  88. }
  89. public int ExecuteNonQuery () {
  90. IntPtr pgResult; // PGresult
  91. int rowsAffected = -1;
  92. ExecStatusType execStatus;
  93. String rowsAffectedString;
  94. string query;
  95. if(conn.State != ConnectionState.Open)
  96. throw new InvalidOperationException(
  97. "ConnnectionState is not Open");
  98. query = TweakQuery(sql, cmdType);
  99. // FIXME: PQexec blocks
  100. // while PQsendQuery is non-blocking
  101. // which is better to use?
  102. // int PQsendQuery(PGconn *conn,
  103. // const char *query);
  104. // execute SQL command
  105. // uses internal property to get the PGConn IntPtr
  106. pgResult = PostgresLibrary.
  107. PQexec (conn.PostgresConnection, query);
  108. execStatus = PostgresLibrary.
  109. PQresultStatus (pgResult);
  110. if(execStatus == ExecStatusType.PGRES_COMMAND_OK) {
  111. rowsAffectedString = PostgresLibrary.
  112. PQcmdTuples (pgResult);
  113. if(rowsAffectedString != null)
  114. if(rowsAffectedString.Equals("") == false)
  115. rowsAffected = int.Parse(rowsAffectedString);
  116. PostgresLibrary.PQclear (pgResult);
  117. }
  118. else {
  119. String errorMessage;
  120. errorMessage = PostgresLibrary.
  121. PQresStatus(execStatus);
  122. errorMessage += " " + PostgresLibrary.
  123. PQresultErrorMessage(pgResult);
  124. throw new SqlException(0, 0,
  125. errorMessage, 0, "",
  126. conn.DataSource, "SqlCommand", 0);
  127. }
  128. return rowsAffected;
  129. }
  130. [MonoTODO]
  131. IDataReader IDbCommand.ExecuteReader () {
  132. return ExecuteReader ();
  133. }
  134. [MonoTODO]
  135. public SqlDataReader ExecuteReader () {
  136. return ExecuteReader(CommandBehavior.Default);
  137. }
  138. [MonoTODO]
  139. IDataReader IDbCommand.ExecuteReader (
  140. CommandBehavior behavior) {
  141. return ExecuteReader (behavior);
  142. }
  143. [MonoTODO]
  144. public SqlDataReader ExecuteReader (CommandBehavior behavior)
  145. {
  146. if(conn.State != ConnectionState.Open)
  147. throw new InvalidOperationException(
  148. "ConnectionState is not Open");
  149. cmdBehavior = behavior;
  150. queries = null;
  151. currentQuery = -1;
  152. dataReader = new SqlDataReader(this);
  153. if((behavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult) {
  154. queries = new String[1];
  155. queries[0] = sql;
  156. }
  157. else {
  158. queries = sql.Split(new Char[] {';'});
  159. }
  160. dataReader.NextResult();
  161. return dataReader;
  162. }
  163. internal SqlResult NextResult()
  164. {
  165. SqlResult res = new SqlResult();
  166. res.Connection = this.Connection;
  167. string statement;
  168. currentQuery++;
  169. if(currentQuery < queries.Length && queries[currentQuery].Equals("") == false) {
  170. statement = TweakQuery(queries[currentQuery], cmdType);
  171. ExecuteQuery(statement, res);
  172. res.ResultReturned = true;
  173. }
  174. else {
  175. res.ResultReturned = false;
  176. }
  177. return res;
  178. }
  179. private string TweakQuery(string query, CommandType commandType) {
  180. string statement = "";
  181. StringBuilder td;
  182. // TODO: need to handle parameters
  183. // finish building SQL based on CommandType
  184. switch(commandType) {
  185. case CommandType.Text:
  186. statement = query;
  187. break;
  188. case CommandType.StoredProcedure:
  189. statement =
  190. "SELECT " + query + "()";
  191. break;
  192. case CommandType.TableDirect:
  193. string[] directTables = query.Split(
  194. new Char[] {','});
  195. td = new StringBuilder("SELECT * FROM ");
  196. for(int tab = 0; tab < directTables.Length; tab++) {
  197. if(tab > 0 && tab < directTables.Length - 1)
  198. td.Append(',');
  199. td.Append(directTables[tab]);
  200. // FIXME: if multipe tables, how do we
  201. // join? based on Primary/Foreign Keys?
  202. // Otherwise, a Cartesian Product happens
  203. }
  204. statement = td.ToString();
  205. break;
  206. default:
  207. // FIXME: throw an exception?
  208. statement = query;
  209. break;
  210. }
  211. return statement;
  212. }
  213. private void ExecuteQuery (string query, SqlResult res)
  214. {
  215. IntPtr pgResult;
  216. ExecStatusType execStatus;
  217. if(conn.State != ConnectionState.Open)
  218. throw new InvalidOperationException(
  219. "ConnectionState is not Open");
  220. // FIXME: PQexec blocks
  221. // while PQsendQuery is non-blocking
  222. // which is better to use?
  223. // int PQsendQuery(PGconn *conn,
  224. // const char *query);
  225. // execute SQL command
  226. // uses internal property to get the PGConn IntPtr
  227. pgResult = PostgresLibrary.
  228. PQexec (conn.PostgresConnection, query);
  229. execStatus = PostgresLibrary.
  230. PQresultStatus (pgResult);
  231. if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
  232. res.BuildTableSchema(pgResult);
  233. }
  234. else {
  235. String errorMessage;
  236. errorMessage = PostgresLibrary.
  237. PQresStatus(execStatus);
  238. errorMessage += " " + PostgresLibrary.
  239. PQresultErrorMessage(pgResult);
  240. throw new SqlException(0, 0,
  241. errorMessage, 0, "",
  242. conn.DataSource, "SqlCommand", 0);
  243. }
  244. }
  245. // since SqlCommand has resources so SqlDataReader
  246. // can do Read() and NextResult(), need to free
  247. // those resources. Also, need to allow this SqlCommand
  248. // and this SqlConnection to things again.
  249. internal void CloseReader() {
  250. conn.OpenReader = false;
  251. dataReader = null;
  252. queries = null;
  253. }
  254. /// <summary>
  255. /// ExecuteScalar is used to retrieve one object
  256. /// from one result set
  257. /// that has one row and one column.
  258. /// It is lightweight compared to ExecuteReader.
  259. /// </summary>
  260. [MonoTODO]
  261. public object ExecuteScalar () {
  262. IntPtr pgResult; // PGresult
  263. ExecStatusType execStatus;
  264. object obj = null; // return
  265. int nRow = 0; // first row
  266. int nCol = 0; // first column
  267. String value;
  268. int nRows;
  269. int nFields;
  270. string query;
  271. if(conn.State != ConnectionState.Open)
  272. throw new InvalidOperationException(
  273. "ConnnectionState is not Open");
  274. query = TweakQuery(sql, cmdType);
  275. // FIXME: PQexec blocks
  276. // while PQsendQuery is non-blocking
  277. // which is better to use?
  278. // int PQsendQuery(PGconn *conn,
  279. // const char *query);
  280. // execute SQL command
  281. // uses internal property to get the PGConn IntPtr
  282. pgResult = PostgresLibrary.
  283. PQexec (conn.PostgresConnection, query);
  284. execStatus = PostgresLibrary.
  285. PQresultStatus (pgResult);
  286. if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
  287. nRows = PostgresLibrary.
  288. PQntuples(pgResult);
  289. nFields = PostgresLibrary.
  290. PQnfields(pgResult);
  291. if(nRows > 0 && nFields > 0) {
  292. // get column name
  293. //String fieldName;
  294. //fieldName = PostgresLibrary.
  295. // PQfname(pgResult, nCol);
  296. int oid;
  297. string sType;
  298. DbType dbType;
  299. // get PostgreSQL data type (OID)
  300. oid = PostgresLibrary.
  301. PQftype(pgResult, nCol);
  302. sType = PostgresHelper.
  303. OidToTypname (oid, conn.Types);
  304. dbType = PostgresHelper.
  305. TypnameToSqlDbType(sType);
  306. int definedSize;
  307. // get defined size of column
  308. definedSize = PostgresLibrary.
  309. PQfsize(pgResult, nCol);
  310. // get data value
  311. value = PostgresLibrary.
  312. PQgetvalue(
  313. pgResult,
  314. nRow, nCol);
  315. int columnIsNull;
  316. // is column NULL?
  317. columnIsNull = PostgresLibrary.
  318. PQgetisnull(pgResult,
  319. nRow, nCol);
  320. int actualLength;
  321. // get Actual Length
  322. actualLength = PostgresLibrary.
  323. PQgetlength(pgResult,
  324. nRow, nCol);
  325. obj = PostgresHelper.
  326. ConvertDbTypeToSystem (
  327. dbType,
  328. value);
  329. }
  330. // close result set
  331. PostgresLibrary.PQclear (pgResult);
  332. pgResult = IntPtr.Zero;
  333. }
  334. else {
  335. String errorMessage;
  336. errorMessage = PostgresLibrary.
  337. PQresStatus(execStatus);
  338. errorMessage += " " + PostgresLibrary.
  339. PQresultErrorMessage(pgResult);
  340. throw new SqlException(0, 0,
  341. errorMessage, 0, "",
  342. conn.DataSource, "SqlCommand", 0);
  343. }
  344. return obj;
  345. }
  346. [MonoTODO]
  347. public XmlReader ExecuteXmlReader () {
  348. throw new NotImplementedException ();
  349. }
  350. [MonoTODO]
  351. public void Prepare () {
  352. // FIXME: parameters have to be implemented for this
  353. throw new NotImplementedException ();
  354. }
  355. [MonoTODO]
  356. public SqlCommand Clone () {
  357. throw new NotImplementedException ();
  358. }
  359. #endregion // Methods
  360. #region Properties
  361. public string CommandText {
  362. get {
  363. return sql;
  364. }
  365. set {
  366. sql = value;
  367. }
  368. }
  369. public int CommandTimeout {
  370. get {
  371. return timeout;
  372. }
  373. set {
  374. // FIXME: if value < 0, throw
  375. // ArgumentException
  376. // if (value < 0)
  377. // throw ArgumentException;
  378. timeout = value;
  379. }
  380. }
  381. public CommandType CommandType {
  382. get {
  383. return cmdType;
  384. }
  385. set {
  386. cmdType = value;
  387. }
  388. }
  389. // FIXME: for property Connection, is this the correct
  390. // way to handle a return of a stronger type?
  391. IDbConnection IDbCommand.Connection {
  392. get {
  393. return Connection;
  394. }
  395. set {
  396. // FIXME: throw an InvalidOperationException
  397. // if the change was during a
  398. // transaction in progress
  399. // csc
  400. Connection = (SqlConnection) value;
  401. // mcs
  402. // Connection = value;
  403. // FIXME: set Transaction property to null
  404. }
  405. }
  406. public SqlConnection Connection {
  407. get {
  408. // conn defaults to null
  409. return conn;
  410. }
  411. set {
  412. // FIXME: throw an InvalidOperationException
  413. // if the change was during
  414. // a transaction in progress
  415. conn = value;
  416. // FIXME: set Transaction property to null
  417. }
  418. }
  419. public bool DesignTimeVisible {
  420. get {
  421. return designTime;
  422. }
  423. set{
  424. designTime = value;
  425. }
  426. }
  427. // FIXME; for property Parameters, is this the correct
  428. // way to handle a stronger return type?
  429. IDataParameterCollection IDbCommand.Parameters {
  430. get {
  431. return Parameters;
  432. }
  433. }
  434. SqlParameterCollection Parameters {
  435. get {
  436. return parmCollection;
  437. }
  438. }
  439. // FIXME: for property Transaction, is this the correct
  440. // way to handle a return of a stronger type?
  441. IDbTransaction IDbCommand.Transaction {
  442. get {
  443. return Transaction;
  444. }
  445. set {
  446. // FIXME: error handling - do not allow
  447. // setting of transaction if transaction
  448. // has already begun
  449. // csc
  450. Transaction = (SqlTransaction) value;
  451. // mcs
  452. // Transaction = value;
  453. }
  454. }
  455. public SqlTransaction Transaction {
  456. get {
  457. return trans;
  458. }
  459. set {
  460. // FIXME: error handling
  461. trans = value;
  462. }
  463. }
  464. [MonoTODO]
  465. public UpdateRowSource UpdatedRowSource {
  466. // FIXME: do this once DbDataAdaptor
  467. // and DataRow are done
  468. get {
  469. throw new NotImplementedException ();
  470. }
  471. set {
  472. throw new NotImplementedException ();
  473. }
  474. }
  475. #endregion // Properties
  476. #region Inner Classes
  477. #endregion // Inner Classes
  478. #region Destructors
  479. [MonoTODO]
  480. public void Dispose() {
  481. // FIXME: need proper way to release resources
  482. // Dispose(true);
  483. }
  484. [MonoTODO]
  485. ~SqlCommand() {
  486. // FIXME: need proper way to release resources
  487. // Dispose(false);
  488. }
  489. #endregion //Destructors
  490. }
  491. // SqlResult is used for passing Result Set data
  492. // from SqlCommand to SqlDataReader
  493. internal class SqlResult {
  494. private DataTable dataTableSchema; // only will contain the schema
  495. private IntPtr pg_result; // native PostgreSQL PGresult
  496. private int rowCount;
  497. private int fieldCount;
  498. private string[] pgtypes; // PostgreSQL types (typname)
  499. private bool resultReturned = false;
  500. private SqlConnection con;
  501. internal SqlConnection Connection {
  502. set {
  503. con = value;
  504. }
  505. }
  506. internal bool ResultReturned {
  507. get {
  508. return resultReturned;
  509. }
  510. set {
  511. resultReturned = value;
  512. }
  513. }
  514. internal DataTable Table {
  515. get {
  516. return dataTableSchema;
  517. }
  518. }
  519. internal IntPtr PgResult {
  520. get {
  521. return pg_result;
  522. }
  523. }
  524. internal int RowCount {
  525. get {
  526. return rowCount;
  527. }
  528. }
  529. internal int FieldCount {
  530. get {
  531. return fieldCount;
  532. }
  533. }
  534. internal string[] PgTypes {
  535. get {
  536. return pgtypes;
  537. }
  538. }
  539. internal void BuildTableSchema (IntPtr pgResult) {
  540. pg_result = pgResult;
  541. int nCol;
  542. dataTableSchema = new DataTable();
  543. rowCount = PostgresLibrary.
  544. PQntuples(pgResult);
  545. fieldCount = PostgresLibrary.
  546. PQnfields(pgResult);
  547. int oid;
  548. pgtypes = new string[fieldCount];
  549. for(nCol = 0; nCol < fieldCount; nCol++) {
  550. DbType dbType;
  551. // get column name
  552. String fieldName;
  553. fieldName = PostgresLibrary.
  554. PQfname(pgResult, nCol);
  555. // get PostgreSQL data type (OID)
  556. oid = PostgresLibrary.
  557. PQftype(pgResult, nCol);
  558. pgtypes[nCol] = PostgresHelper.
  559. OidToTypname (oid, con.Types);
  560. int definedSize;
  561. // get defined size of column
  562. definedSize = PostgresLibrary.
  563. PQfsize(pgResult, nCol);
  564. // build the data column and add it the table
  565. DataColumn dc = new DataColumn(fieldName);
  566. dbType = PostgresHelper.
  567. TypnameToSqlDbType(pgtypes[nCol]);
  568. dc.DataType = PostgresHelper.
  569. DbTypeToSystemType(dbType);
  570. dc.MaxLength = definedSize;
  571. dc.SetTable(dataTableSchema);
  572. dataTableSchema.Columns.Add(dc);
  573. }
  574. }
  575. }
  576. }