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