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