Added tests for Task.WhenAll w/ empty list
[mono.git] / mcs / class / System.Data.OracleClient / System.Data.OracleClient / OracleCommand.cs
1 //
2 // OracleCommand.cs
3 //
4 // Part of the Mono class libraries at
5 // mcs/class/System.Data.OracleClient/System.Data.OracleClient
6 //
7 // Assembly: System.Data.OracleClient.dll
8 // Namespace: System.Data.OracleClient
9 //
10 // Authors:
11 //    Daniel Morgan <danielmorgan@verizon.net>
12 //    Tim Coleman <tim@timcoleman.com>
13 //    Marek Safar <marek.safar@gmail.com>
14 //
15 // Copyright (C) Daniel Morgan, 2002, 2004-2005
16 // Copyright (C) Tim Coleman , 2003
17 //
18 // Licensed under the MIT/X11 License.
19 //
20
21 using System;
22 using System.ComponentModel;
23 using System.Data;
24 #if NET_2_0
25 using System.Data.Common;
26 #endif
27 using System.Data.OracleClient.Oci;
28 using System.Drawing.Design;
29 using System.Text;
30
31 namespace System.Data.OracleClient
32 {
33 #if NET_2_0
34         [DefaultEvent ("RecordsAffected")]
35 #endif
36         [Designer ("Microsoft.VSDesigner.Data.VS.OracleCommandDesigner, " + Consts.AssemblyMicrosoft_VSDesigner)]
37         [ToolboxItem (true)]
38         public sealed class OracleCommand :
39 #if NET_2_0
40                 DbCommand, ICloneable
41 #else
42                 Component, ICloneable, IDbCommand
43 #endif
44         {
45                 #region Fields
46
47                 CommandBehavior behavior;
48                 string commandText;
49                 CommandType commandType;
50                 OracleConnection connection;
51                 bool designTimeVisible;
52                 OracleParameterCollection parameters;
53                 OracleTransaction transaction;
54                 UpdateRowSource updatedRowSource;
55                 OciStatementHandle preparedStatement;
56                 
57                 int moreResults;
58
59                 #endregion // Fields
60
61                 #region Constructors
62
63                 public OracleCommand ()
64                         : this (String.Empty, null, null)
65                 {
66                 }
67
68                 public OracleCommand (string commandText)
69                         : this (commandText, null, null)
70                 {
71                 }
72
73                 public OracleCommand (string commandText, OracleConnection connection)
74                         : this (commandText, connection, null)
75                 {
76                 }
77
78                 public OracleCommand (string commandText, OracleConnection connection, OracleTransaction tx)
79                 {
80                         moreResults = -1;
81                         preparedStatement = null;
82                         CommandText = commandText;
83                         Connection = connection;
84                         Transaction = tx;
85                         CommandType = CommandType.Text;
86                         UpdatedRowSource = UpdateRowSource.Both;
87                         DesignTimeVisible = true;
88                         parameters = new OracleParameterCollection ();
89                 }
90
91                 #endregion // Constructors
92
93                 #region Properties
94
95                 [DefaultValue ("")]
96                 [RefreshProperties (RefreshProperties.All)]
97                 [Editor ("Microsoft.VSDesigner.Data.Oracle.Design.OracleCommandTextEditor, " + Consts.AssemblyMicrosoft_VSDesigner, typeof(UITypeEditor))]
98                 public
99 #if NET_2_0
100                 override
101 #endif
102                 string CommandText {
103                         get {
104                                 if (commandText == null)
105                                         return string.Empty;
106
107                                 return commandText;
108                         }
109                         set { commandText = value; }
110                 }
111
112                 [RefreshProperties (RefreshProperties.All)]
113                 [DefaultValue (CommandType.Text)]
114                 public
115 #if NET_2_0
116                 override
117 #endif
118                 CommandType CommandType {
119                         get { return commandType; }
120                         set {
121                                 if (value == CommandType.TableDirect)
122                                         throw new ArgumentException ("OracleClient provider does not support TableDirect CommandType.");
123                                 commandType = value;
124                         }
125                 }
126
127                 [DefaultValue (null)]
128                 [Editor ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner, typeof(UITypeEditor))]
129                 public
130 #if NET_2_0
131                 new
132 #endif
133                 OracleConnection Connection {
134                         get { return connection; }
135                         set { connection = value; }
136                 }
137
138 #if NET_2_0
139                 [Browsable (false)]
140                 [EditorBrowsable (EditorBrowsableState.Never)]
141                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
142                 public override int CommandTimeout {
143                         get { return 0; }
144                         set { }
145                 }
146
147                 [MonoTODO]
148                 protected override DbConnection DbConnection {
149                         get { return Connection; }
150                         set { Connection = (OracleConnection) value; }
151                 }
152
153                 [MonoTODO]
154                 protected override DbParameterCollection DbParameterCollection {
155                         get { return Parameters; }
156                 }
157
158                 [MonoTODO]
159                 protected override DbTransaction DbTransaction {
160                         get { return Transaction; }
161                         set { Transaction = (OracleTransaction) value; }
162                 }
163 #endif
164
165                 [DefaultValue (true)]
166                 [Browsable (false)]
167                 [DesignOnly (true)]
168 #if NET_2_0
169                 [EditorBrowsable (EditorBrowsableState.Never)]
170 #endif
171                 public
172 #if NET_2_0
173                 override
174 #endif
175                 bool DesignTimeVisible {
176                         get { return designTimeVisible; }
177                         set { designTimeVisible = value; }
178                 }
179
180                 internal OciEnvironmentHandle Environment {
181                         get { return Connection.Environment; }
182                 }
183
184                 internal OciErrorHandle ErrorHandle {
185                         get { return Connection.ErrorHandle; }
186                 }
187
188 #if !NET_2_0
189                 int IDbCommand.CommandTimeout {
190                         get { return 0; }
191                         set { }
192                 }
193
194                 [Editor ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner, typeof(UITypeEditor))]
195                 [DefaultValue (null)]
196                 IDbConnection IDbCommand.Connection {
197                         get { return Connection; }
198                         set {
199                                 // InvalidCastException is expected when types do not match
200                                 Connection = (OracleConnection) value;
201                         }
202                 }
203
204                 IDataParameterCollection IDbCommand.Parameters {
205                         get { return Parameters; }
206                 }
207
208                 IDbTransaction IDbCommand.Transaction {
209                         get { return Transaction; }
210                         set {
211                                 // InvalidCastException is expected when types do not match
212                                 Transaction = (OracleTransaction) value;
213                         }
214                 }
215 #endif
216
217                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
218                 public
219 #if NET_2_0
220                 new
221 #endif
222                 OracleParameterCollection Parameters {
223                         get { return parameters; }
224                 }
225
226                 [Browsable (false)]
227                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
228                 public
229 #if NET_2_0
230                 new
231 #endif
232                 OracleTransaction Transaction {
233                         get { return transaction; }
234                         set { transaction = value; }
235                 }
236
237                 [DefaultValue (UpdateRowSource.Both)]
238                 public
239 #if NET_2_0
240                 override
241 #endif
242                 UpdateRowSource UpdatedRowSource {
243                         get { return updatedRowSource; }
244                         set { updatedRowSource = value; }
245                 }
246
247                 #endregion
248
249                 #region Methods
250
251                 private void AssertCommandTextIsSet ()
252                 {
253                         if (CommandText.Length == 0)
254                                 throw new InvalidOperationException ("The command text for this Command has not been set.");
255                 }
256
257                 private void AssertConnectionIsOpen ()
258                 {
259                         if (Connection == null || Connection.State == ConnectionState.Closed)
260                                 throw new InvalidOperationException ("An open Connection object is required to continue.");
261                 }
262
263                 private void AssertTransactionMatch ()
264                 {
265                         if (Connection.Transaction != null && Transaction != Connection.Transaction)
266                                 throw new InvalidOperationException ("Execute requires the Command object to have a Transaction object when the Connection object assigned to the command is in a pending local transaction.  The Transaction property of the Command has not been initialized.");
267                 }
268
269                 private void BindParameters (OciStatementHandle statement)
270                 {
271                         for (int p = 0; p < Parameters.Count; p++)
272                                 Parameters[p].Bind (statement, Connection, (uint) p);
273                 }
274
275                 [MonoTODO]
276                 public
277 #if NET_2_0
278                 override
279 #endif
280                 void Cancel ()
281                 {
282                         throw new NotImplementedException ();
283                 }
284
285                 [MonoTODO]
286                 public object Clone ()
287                 {
288                         // create a new OracleCommand object with the same properties
289
290                         OracleCommand cmd = new OracleCommand ();
291
292                         cmd.CommandText = this.CommandText;
293                         cmd.CommandType = this.CommandType;
294
295                         // FIXME: not sure if I should set the same object here
296                         // or get a clone of these too
297                         cmd.Connection = this.Connection;
298                         cmd.Transaction = this.Transaction;
299
300                         foreach (OracleParameter parm in this.Parameters) {
301
302                                 OracleParameter newParm = cmd.CreateParameter ();
303
304                                 newParm.DbType = parm.DbType;
305                                 newParm.Direction = parm.Direction;
306                                 newParm.IsNullable = parm.IsNullable;
307                                 newParm.Offset = parm.Offset;
308                                 newParm.OracleType = parm.OracleType;
309                                 newParm.ParameterName = parm.ParameterName;
310                                 //newParm.Precision = parm.Precision;
311                                 //newParm.Scale = parm.Scale;
312                                 newParm.SourceColumn = parm.SourceColumn;
313                                 newParm.SourceVersion = parm.SourceVersion;
314                                 newParm.Value = parm.Value;
315
316                                 cmd.Parameters.Add (newParm);
317                         }
318
319                         //cmd.Container = this.Container;
320                         cmd.DesignTimeVisible = this.DesignTimeVisible;
321                         //cmd.DesignMode = this.DesignMode;
322                         cmd.Site = this.Site;
323                         //cmd.UpdateRowSource = this.UpdateRowSource;
324
325                         return cmd;
326                 }
327
328 #if NET_2_0
329                 protected override DbParameter CreateDbParameter ()
330                 {
331                         return CreateParameter ();
332                 }
333
334                 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
335                 {
336                         return ExecuteReader (behavior);
337                 }
338 #endif
339
340                 internal void UpdateParameterValues ()
341                 {
342                         moreResults = -1;
343                         if (Parameters.Count > 0) {
344                                 bool foundCursor = false;
345                                 for (int p = 0; p < Parameters.Count; p++) {
346                                         OracleParameter parm = Parameters [p];
347                                         if (parm.OracleType.Equals (OracleType.Cursor)) {
348                                                 if (!foundCursor && parm.Direction != ParameterDirection.Input) {
349                                                         // if there are multiple REF CURSORs,
350                                                         // you only can get the first cursor for now
351                                                         // because user of OracleDataReader
352                                                         // will do a NextResult to get the next 
353                                                         // REF CURSOR (if it exists)
354                                                         foundCursor = true;
355                                                         parm.Update (this);
356                                                         if (p + 1 == Parameters.Count)
357                                                                 moreResults = -1;
358                                                         else
359                                                                 moreResults = p;
360                                                 }
361                                         } else
362                                                 parm.Update (this);
363                                 }
364                         }
365                 }
366
367                 internal void CloseDataReader ()
368                 {
369                         Connection.DataReader = null;
370                         if ((behavior & CommandBehavior.CloseConnection) != 0)
371                                 Connection.Close ();
372                 }
373
374                 public
375 #if NET_2_0
376                 new
377 #endif
378                 OracleParameter CreateParameter ()
379                 {
380                         return new OracleParameter ();
381                 }
382
383                 internal void DeriveParameters ()
384                 {
385                         if (commandType != CommandType.StoredProcedure)
386                                 throw new InvalidOperationException (String.Format ("OracleCommandBuilder DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
387
388                         //OracleParameterCollection localParameters = new OracleParameterCollection (this);
389
390                         throw new NotImplementedException ();
391                 }
392
393                 private int ExecuteNonQueryInternal (OciStatementHandle statement, bool useAutoCommit)
394                 {
395                         moreResults = -1;
396
397                         if (preparedStatement == null)
398                                 PrepareStatement (statement);
399
400                         bool isNonQuery = IsNonQuery (statement);
401
402                         BindParameters (statement);
403                         if (isNonQuery == true)
404                                 statement.ExecuteNonQuery (useAutoCommit);
405                         else
406                                 statement.ExecuteQuery (false);
407
408                         UpdateParameterValues ();
409
410                         int rowsAffected = statement.GetAttributeInt32 (OciAttributeType.RowCount, ErrorHandle);
411
412                         return rowsAffected;
413                 }
414
415                 public
416 #if NET_2_0
417                 override
418 #endif
419                 int ExecuteNonQuery ()
420                 {
421                         moreResults = -1;
422
423                         AssertConnectionIsOpen ();
424                         AssertTransactionMatch ();
425                         AssertCommandTextIsSet ();
426                         bool useAutoCommit = false;
427
428                         if (Transaction != null)
429                                 Transaction.AttachToServiceContext ();
430                         else
431                                 useAutoCommit = true;
432
433                         OciStatementHandle statement = GetStatementHandle ();
434                         try {
435                                 return ExecuteNonQueryInternal (statement, useAutoCommit);
436                         } finally {
437                                 SafeDisposeHandle (statement);
438                         }
439                 }
440
441                 public int ExecuteOracleNonQuery (out OracleString rowid)
442                 {
443                         moreResults = -1;
444
445                         AssertConnectionIsOpen ();
446                         AssertTransactionMatch ();
447                         AssertCommandTextIsSet ();
448                         bool useAutoCommit = false;
449
450                         if (Transaction != null)
451                                 Transaction.AttachToServiceContext ();
452                         else
453                                 useAutoCommit = true;
454
455                         OciStatementHandle statement = GetStatementHandle ();
456
457                         try {
458                                 int retval = ExecuteNonQueryInternal (statement, useAutoCommit);
459                                 OciRowIdDescriptor rowIdDescriptor = statement.GetAttributeRowIdDescriptor (ErrorHandle, Environment);
460                                 string srowid = rowIdDescriptor.GetRowIdToString (ErrorHandle);
461                                 rowid = new OracleString (srowid);
462                                 rowIdDescriptor = null;
463                                 return retval;
464                         } finally {
465                                 SafeDisposeHandle (statement);
466                         }
467                 }
468
469                 public object ExecuteOracleScalar ()
470                 {
471                         moreResults = -1;
472
473                         object output = DBNull.Value;
474
475                         AssertConnectionIsOpen ();
476                         AssertTransactionMatch ();
477                         AssertCommandTextIsSet ();
478
479                         if (Transaction != null)
480                                 Transaction.AttachToServiceContext ();
481
482                         OciStatementHandle statement = GetStatementHandle ();
483                         try {
484                                 if (preparedStatement == null)
485                                         PrepareStatement (statement);
486
487                                 bool isNonQuery = IsNonQuery (statement);
488
489                                 BindParameters (statement);
490
491                                 if (isNonQuery == true)
492                                         ExecuteNonQueryInternal (statement, false);
493                                 else {
494                                         statement.ExecuteQuery (false);
495
496                                         if (statement.Fetch ()) {
497                                                 OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [0];
498                                                 if (!defineHandle.IsNull)
499                                                         output = defineHandle.GetOracleValue (Connection.SessionFormatProvider, Connection);
500                                                 switch (defineHandle.DataType) {
501                                                 case OciDataType.Blob:
502                                                 case OciDataType.Clob:
503                                                         ((OracleLob) output).connection = Connection;
504                                                         break;
505                                                 }
506                                         }
507                                         UpdateParameterValues ();
508                                 }
509
510                                 return output;
511                         } finally {
512                                 SafeDisposeHandle (statement);
513                         }
514                 }
515
516                 private bool IsNonQuery (OciStatementHandle statementHandle)
517                 {
518                         // assumes Prepare() has been called prior to calling this function
519
520                         OciStatementType statementType = statementHandle.GetStatementType ();
521                         if (statementType.Equals (OciStatementType.Select))
522                                 return false;
523
524                         return true;
525                 }
526
527                 public
528 #if NET_2_0
529                 new
530 #endif
531                 OracleDataReader ExecuteReader ()
532                 {
533                         return ExecuteReader (CommandBehavior.Default);
534                 }
535
536                 public
537 #if NET_2_0
538                 new
539 #endif
540                 OracleDataReader ExecuteReader (CommandBehavior behavior)
541                 {
542                         AssertConnectionIsOpen ();
543                         AssertTransactionMatch ();
544                         AssertCommandTextIsSet ();
545
546                         moreResults = -1;
547
548                         bool hasRows = false;
549
550                         this.behavior = behavior;
551
552                         if (Transaction != null)
553                                 Transaction.AttachToServiceContext ();
554
555                         OciStatementHandle statement = GetStatementHandle ();
556                         OracleDataReader rd = null;
557
558                         try {
559                                 if (preparedStatement == null)
560                                         PrepareStatement (statement);
561                                 else
562                                         preparedStatement = null;       // OracleDataReader releases the statement handle
563
564                                 bool isNonQuery = IsNonQuery (statement);
565
566                                 BindParameters (statement);
567
568                                 if (isNonQuery) 
569                                         ExecuteNonQueryInternal (statement, false);
570                                 else {  
571                                         if ((behavior & CommandBehavior.SchemaOnly) != 0)
572                                                 statement.ExecuteQuery (true);
573                                         else
574                                                 hasRows = statement.ExecuteQuery (false);
575
576                                         UpdateParameterValues ();
577                                 }
578
579                                 if (Parameters.Count > 0) {
580                                         for (int p = 0; p < Parameters.Count; p++) {
581                                                 OracleParameter parm = Parameters [p];
582                                                 if (parm.OracleType.Equals (OracleType.Cursor)) {
583                                                         if (parm.Direction != ParameterDirection.Input) {
584                                                                 rd = (OracleDataReader) parm.Value;
585                                                                 break;
586                                                         }
587                                                 }
588                                         }                                       
589                                 }
590
591                                 if (rd == null)
592                                         rd = new OracleDataReader (this, statement, hasRows, behavior);
593
594                         } finally {
595                                 if (statement != null && rd == null)
596                                         statement.Dispose();
597                         }
598
599                         return rd;
600                 }
601
602                 public
603 #if NET_2_0
604                 override
605 #endif
606                 object ExecuteScalar ()
607                 {
608                         moreResults = -1;
609                         object output = null;//if we find nothing we return this
610
611                         AssertConnectionIsOpen ();
612                         AssertTransactionMatch ();
613                         AssertCommandTextIsSet ();
614
615                         if (Transaction != null)
616                                 Transaction.AttachToServiceContext ();
617
618                         OciStatementHandle statement = GetStatementHandle ();
619                         try {
620                                 if (preparedStatement == null)
621                                         PrepareStatement (statement);
622
623                                 bool isNonQuery = IsNonQuery (statement);
624
625                                 BindParameters (statement);
626
627                                 if (isNonQuery == true)
628                                         ExecuteNonQueryInternal (statement, false);
629                                 else {
630                                         statement.ExecuteQuery (false);
631
632                                         if (statement.Fetch ()) {
633                                                 OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [0];
634                                                 if (!defineHandle.IsNull)
635                                                 {
636                                                         switch (defineHandle.DataType) {
637                                                         case OciDataType.Blob:
638                                                         case OciDataType.Clob:
639                                                                 OracleLob lob = (OracleLob) defineHandle.GetValue (
640                                                                         Connection.SessionFormatProvider, Connection);
641                                                                 lob.connection = Connection;
642                                                                 output = lob.Value;
643                                                                 lob.Close ();
644                                                                 break;
645                                                         default:
646                                                                 output = defineHandle.GetValue (
647                                                                         Connection.SessionFormatProvider, Connection);
648                                                                 break;
649                                                         }
650                                                 }
651                                         }
652                                         UpdateParameterValues ();
653                                 }
654                         } finally {
655                                 SafeDisposeHandle (statement);
656                         }
657
658                         return output;
659                 }
660
661                 internal OciStatementHandle GetNextResult () 
662                 {
663                         if (moreResults == -1)
664                                 return null;
665
666                         if (Parameters.Count > 0) {
667                                 int p = moreResults + 1;
668                                 
669                                 if (p >= Parameters.Count) {
670                                         moreResults = -1;
671                                         return null;
672                                 }
673
674                                 for (; p < Parameters.Count; p++) {
675                                         OracleParameter parm = Parameters [p];
676                                         if (parm.OracleType.Equals (OracleType.Cursor)) {
677                                                 if (parm.Direction != ParameterDirection.Input) {
678                                                         if (p + 1 == Parameters.Count)
679                                                                 moreResults = -1;
680                                                         else 
681                                                                 moreResults = p;
682                                                         return parm.GetOutRefCursor (this);
683                                                         
684                                                 }
685                                         } 
686                                 }
687                         }
688
689                         moreResults = -1;
690                         return null;
691                 }
692
693                 private OciStatementHandle GetStatementHandle ()
694                 {
695                         AssertConnectionIsOpen ();
696                         if (preparedStatement != null)
697                                 return preparedStatement;
698
699                         OciStatementHandle h = (OciStatementHandle) Connection.Environment.Allocate (OciHandleType.Statement);
700                         h.ErrorHandle = Connection.ErrorHandle;
701                         h.Service = Connection.ServiceContext;
702                         h.Command = this;
703                         return h;
704                 }
705
706                 private void SafeDisposeHandle (OciStatementHandle h)
707                 {
708                         if (h != null && h != preparedStatement)
709                                 h.Dispose();
710                 }
711
712 #if !NET_2_0
713                 IDbDataParameter IDbCommand.CreateParameter ()
714                 {
715                         return CreateParameter ();
716                 }
717
718                 IDataReader IDbCommand.ExecuteReader ()
719                 {
720                         return ExecuteReader ();
721                 }
722
723                 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
724                 {
725                         return ExecuteReader (behavior);
726                 }
727 #endif
728
729                 void PrepareStatement (OciStatementHandle statement)
730                 {
731                         if (commandType == CommandType.StoredProcedure) {
732                                 StringBuilder sb = new StringBuilder ();
733                                 if (Parameters.Count > 0)
734                                         foreach (OracleParameter parm in Parameters) {
735                                                 if (sb.Length > 0)
736                                                         sb.Append (",");
737                                                 sb.Append (parm.ParameterName + "=>:" + parm.ParameterName);
738                                         }
739
740                                 string sql = "begin " + commandText + "(" + sb.ToString() + "); end;";
741                                 statement.Prepare (sql);
742                         } else  // Text
743                                 statement.Prepare (commandText);
744                 }
745
746                 public
747 #if NET_2_0
748                 override
749 #endif
750                 void Prepare ()
751                 {
752                         AssertConnectionIsOpen ();
753                         OciStatementHandle statement = GetStatementHandle ();
754                         PrepareStatement (statement);
755                         preparedStatement = statement;
756                 }
757
758                 protected override void Dispose (bool disposing)
759                 {
760                         if (disposing)
761                                 if (Parameters.Count > 0)
762                                         foreach (OracleParameter parm in Parameters)
763                                                 parm.FreeHandle ();
764                         base.Dispose (disposing);
765                 }
766
767                 #endregion // Methods
768         }
769 }