2 // System.Data.SqlClient.SqlCommand.cs
5 // Rodrigo Moya (rodrigo@ximian.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
7 // Tim Coleman (tim@timcoleman.com)
9 // (C) Ximian, Inc 2002 http://www.ximian.com/
10 // (C) Daniel Morgan, 2002
11 // (C) Copyright 2002 Tim Coleman
14 // SQL and concepts were used from libgda 0.8.190 (GNOME Data Access)
\r
15 // http://www.gnome-db.org/
\r
16 // with permission from the authors of the
\r
17 // PostgreSQL provider in libgda:
\r
18 // Michael Lausch <michael@lausch.at>
19 // Rodrigo Moya <rodrigo@gnome-db.org>
20 // Vivien Malerba <malerba@gnome-db.org>
21 // Gonzalo Paniagua Javier <gonzalo@gnome-db.org>
24 // use #define DEBUG_SqlCommand if you want to spew debug messages
25 // #define DEBUG_SqlCommand
28 using System.Collections;
29 using System.ComponentModel;
31 using System.Data.Common;
32 using System.Runtime.InteropServices;
36 namespace System.Data.SqlClient {
38 /// Represents a SQL statement that is executed
39 /// while connected to a SQL database.
41 // public sealed class SqlCommand : Component, IDbCommand, ICloneable
42 public sealed class SqlCommand : IDbCommand {
46 private string sql = "";
47 private int timeout = 30;
48 // default is 30 seconds
49 // for command execution
51 private SqlConnection conn = null;
52 private SqlTransaction trans = null;
53 private CommandType cmdType = CommandType.Text;
54 private bool designTime = false;
55 private SqlParameterCollection parmCollection = new
56 SqlParameterCollection();
58 // SqlDataReader state data for ExecuteReader()
59 private SqlDataReader dataReader = null;
60 private string[] queries = null;
61 private int currentQuery = -1;
62 private CommandBehavior cmdBehavior = CommandBehavior.Default;
64 private ParmUtil parmUtil = null;
74 public SqlCommand (string cmdText) {
78 public SqlCommand (string cmdText, SqlConnection connection) {
83 public SqlCommand (string cmdText, SqlConnection connection,
84 SqlTransaction transaction) {
90 #endregion // Constructors
95 public void Cancel () {
96 // FIXME: use non-blocking Exec for this
97 throw new NotImplementedException ();
100 // FIXME: is this the correct way to return a stronger type?
102 IDbDataParameter IDbCommand.CreateParameter () {
103 return CreateParameter ();
107 public SqlParameter CreateParameter () {
108 return new SqlParameter ();
111 public int ExecuteNonQuery () {
112 IntPtr pgResult; // PGresult
113 int rowsAffected = -1;
114 ExecStatusType execStatus;
115 String rowsAffectedString;
118 if(conn.State != ConnectionState.Open)
119 throw new InvalidOperationException(
120 "ConnnectionState is not Open");
122 query = TweakQuery(sql, cmdType);
124 // FIXME: PQexec blocks
125 // while PQsendQuery is non-blocking
126 // which is better to use?
127 // int PQsendQuery(PGconn *conn,
128 // const char *query);
130 // execute SQL command
131 // uses internal property to get the PGConn IntPtr
132 pgResult = PostgresLibrary.
133 PQexec (conn.PostgresConnection, query);
135 execStatus = PostgresLibrary.
136 PQresultStatus (pgResult);
138 if(execStatus == ExecStatusType.PGRES_COMMAND_OK ||
139 execStatus == ExecStatusType.PGRES_TUPLES_OK ) {
141 rowsAffectedString = PostgresLibrary.
142 PQcmdTuples (pgResult);
144 if(rowsAffectedString != null)
145 if(rowsAffectedString.Equals("") == false)
146 rowsAffected = int.Parse(rowsAffectedString);
148 PostgresLibrary.PQclear (pgResult);
149 pgResult = IntPtr.Zero;
154 errorMessage = PostgresLibrary.
155 PQresStatus(execStatus);
157 errorMessage += " " + PostgresLibrary.
\r
158 PQresultErrorMessage(pgResult);
\r
160 PostgresLibrary.PQclear (pgResult);
161 pgResult = IntPtr.Zero;
163 throw new SqlException(0, 0,
165 conn.DataSource, "SqlCommand", 0);
\r
172 IDataReader IDbCommand.ExecuteReader () {
173 return ExecuteReader ();
177 public SqlDataReader ExecuteReader () {
178 return ExecuteReader(CommandBehavior.Default);
182 IDataReader IDbCommand.ExecuteReader (
183 CommandBehavior behavior) {
184 return ExecuteReader (behavior);
188 public SqlDataReader ExecuteReader (CommandBehavior behavior)
190 if(conn.State != ConnectionState.Open)
191 throw new InvalidOperationException(
192 "ConnectionState is not Open");
194 cmdBehavior = behavior;
198 dataReader = new SqlDataReader(this);
200 queries = sql.Split(new Char[] {';'});
202 dataReader.NextResult();
207 internal SqlResult NextResult()
209 SqlResult res = new SqlResult();
210 res.Connection = this.Connection;
211 res.Behavior = cmdBehavior;
216 res.CurrentQuery = currentQuery;
218 if(currentQuery < queries.Length && queries[currentQuery].Equals("") == false) {
219 res.SQL = queries[currentQuery];
220 statement = TweakQuery(queries[currentQuery], cmdType);
221 ExecuteQuery(statement, res);
222 res.ResultReturned = true;
225 res.ResultReturned = false;
231 private string TweakQuery(string query, CommandType commandType) {
232 string statement = "";
236 Console.WriteLine("---------[][] TweakQuery() [][]--------");
237 Console.WriteLine("CommandType: " + commandType + " CommandBehavior: " + cmdBehavior);
238 Console.WriteLine("SQL before command type: " + query);
240 // finish building SQL based on CommandType
241 switch(commandType) {
242 case CommandType.Text:
245 case CommandType.StoredProcedure:
247 "SELECT " + query + "()";
249 case CommandType.TableDirect:
250 // NOTE: this is for the PostgreSQL provider
251 // and for OleDb, according to the docs,
252 // an exception is thrown if you try to use
253 // this with SqlCommand
254 string[] directTables = query.Split(
257 td = new StringBuilder("SELECT * FROM ");
259 for(int tab = 0; tab < directTables.Length; tab++) {
262 td.Append(directTables[tab]);
263 // FIXME: if multipe tables, how do we
264 // join? based on Primary/Foreign Keys?
265 // Otherwise, a Cartesian Product happens
267 statement = td.ToString();
270 // FIXME: throw an exception?
275 Console.WriteLine("SQL after command type: " + statement);
277 // TODO: this parameters utility
278 // currently only support input variables
279 // need todo output, input/output, and return.
281 Console.WriteLine("using ParmUtil in TweakQuery()...");
283 parmUtil = new ParmUtil(statement, parmCollection);
285 Console.WriteLine("ReplaceWithParms...");
288 statement = parmUtil.ReplaceWithParms();
291 Console.WriteLine("SQL after ParmUtil: " + statement);
296 private void ExecuteQuery (string query, SqlResult res)
300 ExecStatusType execStatus;
302 if(conn.State != ConnectionState.Open)
303 throw new InvalidOperationException(
304 "ConnectionState is not Open");
306 // FIXME: PQexec blocks
307 // while PQsendQuery is non-blocking
308 // which is better to use?
309 // int PQsendQuery(PGconn *conn,
310 // const char *query);
312 // execute SQL command
313 // uses internal property to get the PGConn IntPtr
314 pgResult = PostgresLibrary.
315 PQexec (conn.PostgresConnection, query);
317 execStatus = PostgresLibrary.
318 PQresultStatus (pgResult);
320 res.ExecStatus = execStatus;
322 if(execStatus == ExecStatusType.PGRES_TUPLES_OK ||
323 execStatus == ExecStatusType.PGRES_COMMAND_OK) {
325 res.BuildTableSchema(pgResult);
330 errorMessage = PostgresLibrary.
331 PQresStatus(execStatus);
333 errorMessage += " " + PostgresLibrary.
\r
334 PQresultErrorMessage(pgResult);
\r
336 PostgresLibrary.PQclear (pgResult);
337 pgResult = IntPtr.Zero;
339 throw new SqlException(0, 0,
341 conn.DataSource, "SqlCommand", 0);
\r
345 // since SqlCommand has resources so SqlDataReader
346 // can do Read() and NextResult(), need to free
347 // those resources. Also, need to allow this SqlCommand
348 // and this SqlConnection to do things again.
349 internal void CloseReader() {
353 if((cmdBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection) {
354 conn.CloseReader(true);
357 conn.CloseReader(false);
361 // only meant to be used between SqlConnectioin,
362 // SqlCommand, and SqlDataReader
363 internal void OpenReader(SqlDataReader reader) {
364 conn.OpenReader(reader);
368 /// ExecuteScalar is used to retrieve one object
369 /// from one result set
\r
370 /// that has one row and one column.
\r
371 /// It is lightweight compared to ExecuteReader.
\r
374 public object ExecuteScalar () {
375 IntPtr pgResult; // PGresult
376 ExecStatusType execStatus;
377 object obj = null; // return
378 int nRow = 0; // first row
379 int nCol = 0; // first column
385 if(conn.State != ConnectionState.Open)
386 throw new InvalidOperationException(
387 "ConnnectionState is not Open");
389 query = TweakQuery(sql, cmdType);
391 // FIXME: PQexec blocks
392 // while PQsendQuery is non-blocking
393 // which is better to use?
394 // int PQsendQuery(PGconn *conn,
395 // const char *query);
397 // execute SQL command
398 // uses internal property to get the PGConn IntPtr
399 pgResult = PostgresLibrary.
400 PQexec (conn.PostgresConnection, query);
402 execStatus = PostgresLibrary.
403 PQresultStatus (pgResult);
404 if(execStatus == ExecStatusType.PGRES_COMMAND_OK) {
405 // result was a SQL Command
408 PostgresLibrary.PQclear (pgResult);
409 pgResult = IntPtr.Zero;
411 return null; // return null reference
413 else if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
414 // result was a SQL Query
416 nRows = PostgresLibrary.
419 nFields = PostgresLibrary.
422 if(nRows > 0 && nFields > 0) {
426 //fieldName = PostgresLibrary.
427 // PQfname(pgResult, nCol);
432 // get PostgreSQL data type (OID)
433 oid = PostgresLibrary.
434 PQftype(pgResult, nCol);
435 sType = PostgresHelper.
436 OidToTypname (oid, conn.Types);
437 dbType = PostgresHelper.
438 TypnameToSqlDbType(sType);
441 // get defined size of column
442 definedSize = PostgresLibrary.
443 PQfsize(pgResult, nCol);
446 value = PostgresLibrary.
453 columnIsNull = PostgresLibrary.
454 PQgetisnull(pgResult,
459 actualLength = PostgresLibrary.
460 PQgetlength(pgResult,
463 obj = PostgresHelper.
464 ConvertDbTypeToSystem (
470 PostgresLibrary.PQclear (pgResult);
471 pgResult = IntPtr.Zero;
477 errorMessage = PostgresLibrary.
478 PQresStatus(execStatus);
480 errorMessage += " " + PostgresLibrary.
\r
481 PQresultErrorMessage(pgResult);
\r
483 PostgresLibrary.PQclear (pgResult);
484 pgResult = IntPtr.Zero;
486 throw new SqlException(0, 0,
488 conn.DataSource, "SqlCommand", 0);
\r
495 public XmlReader ExecuteXmlReader () {
496 throw new NotImplementedException ();
500 public void Prepare () {
501 // FIXME: parameters have to be implemented for this
502 throw new NotImplementedException ();
506 public SqlCommand Clone () {
507 throw new NotImplementedException ();
510 #endregion // Methods
514 public string CommandText {
524 public int CommandTimeout {
530 // FIXME: if value < 0, throw
533 // throw ArgumentException;
538 public CommandType CommandType {
548 // FIXME: for property Connection, is this the correct
549 // way to handle a return of a stronger type?
550 IDbConnection IDbCommand.Connection {
556 // FIXME: throw an InvalidOperationException
557 // if the change was during a
558 // transaction in progress
561 Connection = (SqlConnection) value;
563 // Connection = value;
565 // FIXME: set Transaction property to null
569 public SqlConnection Connection {
571 // conn defaults to null
576 // FIXME: throw an InvalidOperationException
577 // if the change was during
578 // a transaction in progress
580 // FIXME: set Transaction property to null
584 public bool DesignTimeVisible {
594 // FIXME; for property Parameters, is this the correct
595 // way to handle a stronger return type?
596 IDataParameterCollection IDbCommand.Parameters {
602 public SqlParameterCollection Parameters {
604 return parmCollection;
608 // FIXME: for property Transaction, is this the correct
609 // way to handle a return of a stronger type?
610 IDbTransaction IDbCommand.Transaction {
616 // FIXME: error handling - do not allow
617 // setting of transaction if transaction
621 Transaction = (SqlTransaction) value;
623 // Transaction = value;
627 public SqlTransaction Transaction {
633 // FIXME: error handling
639 public UpdateRowSource UpdatedRowSource {
640 // FIXME: do this once DbDataAdaptor
641 // and DataRow are done
643 throw new NotImplementedException ();
646 throw new NotImplementedException ();
650 #endregion // Properties
652 #region Inner Classes
654 #endregion // Inner Classes
659 public void Dispose() {
660 // FIXME: need proper way to release resources
666 // FIXME: need proper way to release resources
670 #endregion //Destructors
673 // SqlResult is used for passing Result Set data
674 // from SqlCommand to SqlDataReader
675 internal class SqlResult {
677 private DataTable dataTableSchema = null; // only will contain the schema
678 private IntPtr pg_result = IntPtr.Zero; // native PostgreSQL PGresult
679 private int rowCount = 0;
680 private int fieldCount = 0;
681 private string[] pgtypes = null; // PostgreSQL types (typname)
682 private bool resultReturned = false;
683 private SqlConnection con = null;
684 private int rowsAffected = -1;
685 private ExecStatusType execStatus = ExecStatusType.PGRES_FATAL_ERROR;
686 private int currentQuery = -1;
687 private string sql = "";
688 private CommandBehavior cmdBehavior = CommandBehavior.Default;
690 internal CommandBehavior Behavior {
699 internal string SQL {
708 internal ExecStatusType ExecStatus {
717 internal int CurrentQuery {
723 currentQuery = value;
728 internal SqlConnection Connection {
738 internal int RecordsAffected {
744 internal bool ResultReturned {
746 return resultReturned;
749 resultReturned = value;
753 internal DataTable Table {
755 return dataTableSchema;
759 internal IntPtr PgResult {
765 internal int RowCount {
771 internal int FieldCount {
777 internal string[] PgTypes {
783 internal void BuildTableSchema (IntPtr pgResult) {
784 pg_result = pgResult;
786 // need to set IDataReader.RecordsAffected property
787 string rowsAffectedString;
788 rowsAffectedString = PostgresLibrary.
789 PQcmdTuples (pgResult);
790 if(rowsAffectedString != null)
791 if(rowsAffectedString.Equals("") == false)
792 rowsAffected = int.Parse(rowsAffectedString);
794 // Only Results from SQL SELECT Queries
795 // get a DataTable for schema of the result
796 // otherwise, DataTable is null reference
797 if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
799 dataTableSchema = new DataTable ();
800 dataTableSchema.Columns.Add ("ColumnName", typeof (string));
801 dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int));
802 dataTableSchema.Columns.Add ("ColumnSize", typeof (int));
803 dataTableSchema.Columns.Add ("NumericPrecision", typeof (int));
804 dataTableSchema.Columns.Add ("NumericScale", typeof (int));
805 dataTableSchema.Columns.Add ("IsUnique", typeof (bool));
806 dataTableSchema.Columns.Add ("IsKey", typeof (bool));
807 DataColumn dc = dataTableSchema.Columns["IsKey"];
808 dc.AllowDBNull = true; // IsKey can have a DBNull
809 dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string));
810 dataTableSchema.Columns.Add ("BaseColumnName", typeof (string));
811 dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string));
812 dataTableSchema.Columns.Add ("BaseTableName", typeof (string));
813 dataTableSchema.Columns.Add ("DataType", typeof(string));
814 dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool));
815 dataTableSchema.Columns.Add ("ProviderType", typeof (int));
816 dataTableSchema.Columns.Add ("IsAliased", typeof (bool));
817 dataTableSchema.Columns.Add ("IsExpression", typeof (bool));
818 dataTableSchema.Columns.Add ("IsIdentity", typeof (bool));
819 dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool));
820 dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool));
821 dataTableSchema.Columns.Add ("IsHidden", typeof (bool));
822 dataTableSchema.Columns.Add ("IsLong", typeof (bool));
823 dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool));
825 fieldCount = PostgresLibrary.PQnfields (pgResult);
826 rowCount = PostgresLibrary.PQntuples(pgResult);
827 pgtypes = new string[fieldCount];
\r
829 // TODO: for CommandBehavior.SingleRow
\r
830 // use IRow, otherwise, IRowset
\r
832 if((cmdBehavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow)
\r
835 // TODO: for CommandBehavior.SchemaInfo
836 if((cmdBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
839 // TODO: for CommandBehavior.SingleResult
840 if((cmdBehavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult)
844 // TODO: for CommandBehavior.SequentialAccess - used for reading Large OBjects
845 //if((cmdBehavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess) {
853 for (int i = 0; i < fieldCount; i += 1 ) {
854 schemaRow = dataTableSchema.NewRow ();
856 string columnName = PostgresLibrary.PQfname (pgResult, i);
858 schemaRow["ColumnName"] = columnName;
859 schemaRow["ColumnOrdinal"] = i+1;
860 schemaRow["ColumnSize"] = PostgresLibrary.PQfsize (pgResult, i);
861 schemaRow["NumericPrecision"] = 0;
862 schemaRow["NumericScale"] = 0;
863 if((cmdBehavior & CommandBehavior.SingleResult) == CommandBehavior.KeyInfo) {
864 bool IsUnique, IsKey;
865 GetKeyInfo(columnName, out IsUnique, out IsKey);
868 schemaRow["IsUnique"] = false;
869 schemaRow["IsKey"] = DBNull.Value;
871 schemaRow["BaseCatalogName"] = "";
872 schemaRow["BaseColumnName"] = columnName;
873 schemaRow["BaseSchemaName"] = "";
874 schemaRow["BaseTableName"] = "";
876 // PostgreSQL type to .NET type stuff
877 oid = PostgresLibrary.PQftype (pgResult, i);
878 pgtypes[i] = PostgresHelper.OidToTypname (oid, con.Types);
\r
879 dbType = PostgresHelper.TypnameToSqlDbType (pgtypes[i]);
\r
881 typ = PostgresHelper.DbTypeToSystemType (dbType);
\r
882 string st = typ.ToString();
\r
883 schemaRow["DataType"] = st;
\r
885 schemaRow["AllowDBNull"] = false;
886 schemaRow["ProviderType"] = oid;
887 schemaRow["IsAliased"] = false;
888 schemaRow["IsExpression"] = false;
889 schemaRow["IsIdentity"] = false;
890 schemaRow["IsAutoIncrement"] = false;
891 schemaRow["IsRowVersion"] = false;
892 schemaRow["IsHidden"] = false;
893 schemaRow["IsLong"] = false;
894 schemaRow["IsReadOnly"] = false;
895 schemaRow.AcceptChanges();
896 dataTableSchema.Rows.Add (schemaRow);
900 Console.WriteLine("********** DEBUG Table Schema BEGIN ************");
901 foreach (DataRow myRow in dataTableSchema.Rows) {
\r
902 foreach (DataColumn myCol in dataTableSchema.Columns)
\r
903 Console.WriteLine(myCol.ColumnName + " = " + myRow[myCol]);
\r
904 Console.WriteLine();
\r
906 Console.WriteLine("********** DEBUG Table Schema END ************");
907 #endif // DEBUG_SqlCommand
912 // TODO: how do we get the key info if
913 // we don't have the tableName?
914 private void GetKeyInfo(string columnName, out bool isUnique, out bool isKey) {
921 "SELECT i.indkey, i.indisprimary, i.indisunique " +
922 "FROM pg_class c, pg_class c2, pg_index i " +
923 "WHERE c.relname = ':tableName' AND c.oid = i.indrelid " +
924 "AND i.indexrelid = c2.oid ";