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