6f7b205b9712d0fe6643552f9b6717e7a81e95e4
[mono.git] / mcs / class / System.Data.OracleClient / System.Data.OracleClient / OracleParameter.cs
1 //
2 // OracleParameter.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 //    Tim Coleman <tim@timcoleman.com>
12 //    Daniel Moragn <monodanmorg@yahoo.com>
13 //    Hubert FONGARNAND <informatique.internet@fiducial.fr>
14 //
15 // Copyright (C) Tim Coleman , 2003
16 // Copyright (C) Daniel Morgan, 2005, 2008
17 // Copyright (C) Hubert FONGARNAND, 2005
18 //
19 // Licensed under the MIT/X11 License.
20 //
21
22 using System;
23 using System.Collections;
24 using System.ComponentModel;
25 using System.Data;
26 #if NET_2_0
27 using System.Data.Common;
28 #endif
29 using System.Data.SqlTypes;
30 using System.Data.OracleClient.Oci;
31 using System.Globalization;
32 using System.Runtime.InteropServices;
33 using System.Text;
34
35 namespace System.Data.OracleClient
36 {
37         [TypeConverter (typeof(OracleParameter.OracleParameterConverter))]
38         public sealed class OracleParameter :
39 #if NET_2_0
40                 DbParameter, IDbDataParameter, ICloneable
41 #else
42                 MarshalByRefObject, IDbDataParameter, IDataParameter, ICloneable
43 #endif
44         {
45                 #region Fields
46
47                 string name;
48                 OracleType oracleType = OracleType.VarChar;
49                 OciDataType ociType;
50                 int size;
51                 ParameterDirection direction = ParameterDirection.Input;
52                 bool isNullable;
53                 byte precision;
54                 byte scale;
55                 string srcColumn;
56 #if NET_2_0
57                 bool sourceColumnNullMapping;
58 #endif
59                 DataRowVersion srcVersion;
60                 DbType dbType = DbType.AnsiString;
61                 int offset;
62                 bool sizeSet;
63                 bool oracleTypeSet;
64                 object value = DBNull.Value;
65                 OciLobLocator lobLocator;  // only if Blob or Clob
66                 IntPtr bindOutValue = IntPtr.Zero;
67                 OciDateTimeDescriptor dateTimeDesc;
68                 IntPtr cursor = IntPtr.Zero;
69
70                 OracleParameterCollection container;
71                 OciBindHandle bindHandle;
72                 OracleConnection connection;
73                 byte[] bytes;
74                 IntPtr bindValue = IntPtr.Zero;
75                 bool useRef;
76                 OciDataType bindType;
77                 OracleType bindOracleType;
78
79                 short indicator; 
80                 int bindSize;
81
82                 #endregion // Fields
83
84                 #region Constructors
85
86                 // constructor for cloning the object
87                 private OracleParameter (OracleParameter value)
88                 {
89                         this.name = value.name;
90                         this.oracleType = value.oracleType;
91                         this.ociType = value.ociType;
92                         this.size = value.size;
93                         this.direction = value.direction;
94                         this.isNullable = value.isNullable;
95                         this.precision = value.precision;
96                         this.scale = value.scale;
97                         this.srcColumn = value.srcColumn;
98                         this.srcVersion = value.srcVersion;
99                         this.dbType = value.dbType;
100                         this.offset = value.offset;
101                         this.sizeSet = value.sizeSet;
102                         this.value = value.value;
103                         this.lobLocator = value.lobLocator;
104                         this.oracleTypeSet = value.oracleTypeSet;
105                 }
106
107                 public OracleParameter ()
108                 {
109                         this.name = String.Empty;
110                         this.oracleType = OracleType.VarChar;
111                         this.size = 0;
112                         this.direction = ParameterDirection.Input;
113                         this.isNullable = false;
114                         this.precision = 0;
115                         this.scale = 0;
116                         this.srcColumn = String.Empty;
117                         this.srcVersion = DataRowVersion.Current;
118                         this.value = null;
119                         this.oracleTypeSet = false;
120                 }
121
122                 public OracleParameter (string name, object value)
123                 {
124                         this.name = name;
125                         this.value = value;
126
127                         srcColumn = string.Empty;
128                         SourceVersion = DataRowVersion.Current;
129                         InferOracleType (value);                        
130 #if NET_2_0
131                         // Find the OciType before inferring for the size
132                         if (value != null && value != DBNull.Value) {
133                                 this.sizeSet = true;
134                                 this.size = InferSize ();
135                         }
136 #endif
137                 }
138
139                 public OracleParameter (string name, OracleType oracleType)
140                         : this (name, oracleType, 0, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
141                 {
142                 }
143
144                 public OracleParameter (string name, OracleType oracleType, int size)
145                         : this (name, oracleType, size, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
146                 {
147                 }
148
149                 public OracleParameter (string name, OracleType oracleType, int size, string srcColumn)
150                         : this (name, oracleType, size, ParameterDirection.Input, false, 0, 0, srcColumn, DataRowVersion.Current, null)
151                 {
152                 }
153
154 #if NET_2_0
155                 public OracleParameter (string name, OracleType oracleType, int size, ParameterDirection direction, string sourceColumn, DataRowVersion sourceVersion, bool sourceColumnNullMapping, object value)
156                 {
157                         this.name = name;
158                         if (size < 0)
159                                 throw new ArgumentException("Size must be not be negative.");
160                         
161                         this.value = value;
162                         this.size = size;
163                         // set sizeSet to true iff value is not-null or non-zero size value
164                         if (value != null && value != DBNull.Value && size > 0)
165                                 this.sizeSet = true;
166
167                         SourceColumnNullMapping = sourceColumnNullMapping;
168                         OracleType = oracleType;
169                         Direction = direction;
170                         SourceColumn = sourceColumn;
171                         SourceVersion = sourceVersion;
172                 }
173 #endif
174
175                 public OracleParameter (string name, OracleType oracleType, int size, ParameterDirection direction, bool isNullable, byte precision, byte scale, string srcColumn, DataRowVersion srcVersion, object value)
176                 {
177                         this.name = name;
178                         if (size < 0)
179                                 throw new ArgumentException("Size must be not be negative.");
180                         
181                         this.value = value;
182                         this.size = size;
183                         
184                         // set sizeSet to true iff value is not-null or non-zero size value
185                         if (value != null && value != DBNull.Value && size > 0)
186                                 this.sizeSet = true;
187
188                         this.isNullable = isNullable;
189                         this.precision = precision;
190                         this.scale = scale;
191
192                         OracleType = oracleType;
193                         Direction = direction;
194                         SourceColumn = srcColumn;
195                         SourceVersion = srcVersion;
196                 }
197
198                 #endregion // Constructors
199
200                 #region Properties
201
202                 internal OracleParameterCollection Container {
203                         get { return container; }
204                         set { container = value; }
205                 }
206
207 #if !NET_2_0
208                 [Browsable (false)]
209                 [RefreshProperties (RefreshProperties.All)]
210                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
211 #endif
212                 public
213 #if NET_2_0
214                 override
215 #endif
216                 DbType DbType {
217                         get { return dbType; }
218                         set { SetDbType (value); }
219                 }
220
221 #if !NET_2_0
222                 [DefaultValue (ParameterDirection.Input)]
223 #endif
224                 [RefreshProperties (RefreshProperties.All)]
225                 public
226 #if NET_2_0
227                 override
228 #endif
229                 ParameterDirection Direction {
230                         get { return direction; }
231                         set { direction = value; }
232                 }
233
234 #if !NET_2_0
235                 [Browsable (false)]
236                 [DesignOnly (true)]
237                 [DefaultValue (false)]
238                 [EditorBrowsable (EditorBrowsableState.Never)]
239 #endif
240                 public
241 #if NET_2_0
242                 override
243 #endif
244                 bool IsNullable {
245                         get { return isNullable; }
246                         set { isNullable = value; }
247                 }
248
249 #if NET_2_0
250                 [EditorBrowsable (EditorBrowsableState.Advanced)]
251 #else
252                 [DefaultValue (0)]
253 #endif
254                 [Browsable (false)]
255                 public int Offset {
256                         get { return offset; }
257                         set { offset = value; }
258                 }
259
260                 [DefaultValue (OracleType.VarChar)]
261                 [RefreshProperties (RefreshProperties.All)]
262 #if NET_2_0
263                 [DbProviderSpecificTypeProperty (true)]
264 #endif
265                 public OracleType OracleType {
266                         get { return oracleType; }
267                         set { 
268                                 oracleTypeSet = true;
269                                 SetOracleType (value, false); 
270                         }
271                 }
272
273 #if !NET_2_0
274                 [DefaultValue ("")]
275 #endif
276                 public
277 #if NET_2_0
278                 override
279 #endif
280                 string ParameterName {
281                         get {
282                                 if (name == null)
283                                         return string.Empty;
284                                 return name;
285                         }
286                         set { name = value; }
287                 }
288
289 #if NET_2_0
290                 [Browsable (false)]
291                 [EditorBrowsable (EditorBrowsableState.Never)]
292                 [Obsolete("Set the precision of a decimal use the Math classes.")]
293 #else
294                 [DefaultValue (0)]
295 #endif
296                 public byte Precision {
297                         get { return precision; }
298                         set { /* NO EFFECT*/ }
299                 }
300
301 #if NET_2_0
302                 [Browsable (false)]
303                 [EditorBrowsable (EditorBrowsableState.Never)]
304                 [Obsolete("Set the precision of a decimal use the Math classes.")]
305 #else
306                 [DefaultValue (0)]
307 #endif
308                 public byte Scale {
309                         get { return scale; }
310                         set { /* NO EFFECT*/ }
311                 }
312
313 #if !NET_2_0
314                 [DefaultValue (0)]
315 #endif
316                 public
317 #if NET_2_0
318                 override
319 #endif
320                 int Size {
321                         get { return size; }
322                         set {
323                                 sizeSet = true;
324                                 size = value;
325                         }
326                 }
327
328 #if !NET_2_0
329                 [DefaultValue ("")]
330 #endif
331                 public
332 #if NET_2_0
333                 override
334 #endif
335                 string SourceColumn {
336                         get { return srcColumn; }
337                         set { srcColumn = value; }
338                 }
339
340 #if NET_2_0
341                 [MonoTODO]
342                 public override bool SourceColumnNullMapping {
343                         get { return sourceColumnNullMapping; }
344                         set { sourceColumnNullMapping = value; }
345                 }
346 #endif
347
348 #if !NET_2_0
349                 [DefaultValue ("Current")]
350 #endif
351                 public
352 #if NET_2_0
353                 override
354 #endif
355                 DataRowVersion SourceVersion {
356                         get { return srcVersion; }
357                         set { srcVersion = value; }
358                 }
359
360 #if !NET_2_0
361                 [DefaultValue (null)]
362 #endif
363                 [RefreshProperties (RefreshProperties.All)]
364                 [TypeConverter (typeof(StringConverter))]
365                 public
366 #if NET_2_0
367                 override
368 #endif
369                 object Value {
370                         get { return this.value; }
371                         set {
372                                 this.value = value;
373                                 if (!oracleTypeSet)
374                                         InferOracleType (value);
375 #if NET_2_0
376                                 if (value != null && value != DBNull.Value) {
377                                         this.size = InferSize ();
378                                         this.sizeSet = true;
379                                 }
380 #endif
381                         }
382                 }
383
384                 #endregion // Properties
385
386                 #region Methods
387
388                 private void AssertSizeIsSet ()
389                 {
390                         switch (ociType) {
391                         case OciDataType.VarChar2:
392                         case OciDataType.String:
393                         case OciDataType.VarChar:
394                         case OciDataType.Char:
395                         case OciDataType.CharZ:
396                         case OciDataType.OciString:
397                                 if (!sizeSet)
398                                         throw new Exception ("Size must be set.");
399                                 break;
400                         default:
401                                 break;
402                         }
403                 }
404
405                 internal void Bind (OciStatementHandle statement, OracleConnection con, uint pos)
406                 {
407                         connection = con;
408
409                         if (bindHandle == null)
410                                 bindHandle = new OciBindHandle ((OciHandle) statement);
411
412                         IntPtr tmpHandle = bindHandle.Handle;
413
414                         if (Direction != ParameterDirection.Input)
415                                 AssertSizeIsSet ();
416                         if (!sizeSet)
417                                 size = InferSize ();
418
419                         bindSize = size;
420                         object v = value;
421                         int status = 0;
422                         bindType = ociType;
423                         int rsize = 0;
424
425                         string svalue;
426                         string sDate;
427                         DateTime dt;
428                         bool isnull = false;
429
430                         if (direction == ParameterDirection.Input || direction == ParameterDirection.InputOutput) {
431                                 if (v == null)
432                                         isnull = true;
433                                 else if (v is DBNull)
434                                         isnull = true;
435                                 else {
436                                         INullable mynullable = v as INullable;
437                                         if (mynullable != null)
438                                                 isnull = mynullable.IsNull;
439                                 }                                       
440                         } 
441
442                         if (isnull == true && direction == ParameterDirection.Input) {
443                                 indicator = 0;
444                                 bindType = OciDataType.VarChar2;
445                                 bindSize = 0;
446                         } else {
447                                 switch(ociType) {
448                                 case OciDataType.VarChar2:
449                                 case OciDataType.String:
450                                 case OciDataType.VarChar:
451                                 case OciDataType.Char:
452                                 case OciDataType.CharZ:
453                                 case OciDataType.OciString:
454                                         bindType = OciDataType.String;
455                                         indicator = 0;
456                                         svalue = "\0";
457                                         // convert value from managed type to type to marshal
458                                         if (direction == ParameterDirection.Input || 
459                                                 direction == ParameterDirection.InputOutput) {
460
461                                                 svalue = v.ToString ();
462
463                                                 if (direction == ParameterDirection.Input && size > 0 && svalue.Length > size)
464                                                         svalue = svalue.Substring(0, size);
465
466                                                 svalue = svalue.ToString () + '\0';
467                                         }
468
469                                         // set bind length to size of data
470                                         //bindSize = (size + 1) * 4;
471                                         if (direction == ParameterDirection.Input)
472                                                 bindSize = Encoding.UTF8.GetMaxByteCount (svalue.Length);
473                                         else
474                                                 bindSize = Encoding.UTF8.GetMaxByteCount (size + 1);
475
476                                         // allocate memory based on bind length
477                                         bytes = new byte [bindSize];                            
478
479                                         if (direction == ParameterDirection.Input ||
480                                                 direction == ParameterDirection.InputOutput) {
481                                                 
482                                                 // convert managed type to memory allocated earlier
483                                                 // in this case using OCIUnicodeToCharSet
484                                                 rsize = 0;
485                                                 // Get size of buffer
486                                                 status = OciCalls.OCIUnicodeToCharSet (statement.Parent, null, svalue, out rsize);
487                                                 // Fill buffer
488                                                 status = OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, svalue, out rsize);
489                                         }
490                                         break;
491                                 case OciDataType.Date:
492                                         bindType = OciDataType.Date;
493                                         bindSize = 7;
494                                         // convert value from managed type to type to marshal
495                                         if (direction == ParameterDirection.Input || 
496                                                 direction == ParameterDirection.InputOutput) {
497
498                                                 if (isnull)
499                                                         bytes = new byte [7];
500                                                 else {
501                                                         sDate = "";
502                                                         dt = DateTime.MinValue;
503                                                         if (v is String) {
504                                                                 sDate = (string) v;
505                                                                 dt = DateTime.Parse (sDate);
506                                                         }
507                                                         else if (v is DateTime)
508                                                                 dt = (DateTime) v;
509                                                         else if (v is OracleString) {
510                                                                 sDate = v.ToString ();
511                                                                 dt = DateTime.Parse (sDate);
512                                                         }
513                                                         else if (v is OracleDateTime) {
514                                                                 OracleDateTime odt = (OracleDateTime) v;
515                                                                 dt = (DateTime) odt.Value;
516                                                         }
517                                                         else
518                                                                 throw new NotImplementedException ("For OracleType.DateTime, data type not implemented: " + v.GetType().ToString() + ".");
519
520                                                         // for Input and InputOuput, create byte array and pack DateTime into it
521                                                         bytes = PackDate (dt);
522                                                 }
523                                         } else  {
524                                                 // allocate 7-byte array for Output and ReturnValue to put date
525                                                 bytes = new byte [7];
526                                         }
527                                         break;
528                                 case OciDataType.TimeStamp:
529                                         dateTimeDesc = (OciDateTimeDescriptor) connection.Environment.Allocate (OciHandleType.TimeStamp);
530                                         if (dateTimeDesc == null) {
531                                                 OciErrorInfo info = connection.ErrorHandle.HandleError ();
532                                                 throw new OracleException (info.ErrorCode, info.ErrorMessage);
533                                         }
534                                         dateTimeDesc.ErrorHandle = connection.ErrorHandle;
535                                         bindSize = 11;
536                                         bindType = OciDataType.TimeStamp;
537                                         bindOutValue = dateTimeDesc.Handle;
538                                         bindValue = dateTimeDesc.Handle;
539                                         useRef = true;
540                                         if (direction == ParameterDirection.Input || 
541                                                 direction == ParameterDirection.InputOutput) {
542
543                                                 dt = DateTime.MinValue;
544                                                 sDate = "";
545                                                 if (isnull)
546                                                         indicator = -1;
547                                                 else if (v is String) {
548                                                         sDate = (string) v;
549                                                         dt = DateTime.Parse (sDate);
550                                                 }
551                                                 else if (v is DateTime)
552                                                         dt = (DateTime) v;
553                                                 else if (v is OracleString) {
554                                                         sDate = (string) v;
555                                                         dt = DateTime.Parse (sDate);
556                                                 }
557                                                 else if (v is OracleDateTime) {
558                                                         OracleDateTime odt = (OracleDateTime) v;
559                                                         dt = (DateTime) odt.Value;
560                                                 }
561                                                 else
562                                                         throw new NotImplementedException ("For OracleType.Timestamp, data type not implemented: " + v.GetType().ToString()); // ?
563
564                                                 short year = (short) dt.Year;
565                                                 byte month = (byte) dt.Month;
566                                                 byte day = (byte) dt.Day;
567                                                 byte hour = (byte) dt.Hour;
568                                                 byte min = (byte) dt.Minute;
569                                                 byte sec = (byte) dt.Second;
570                                                 uint fsec = (uint) dt.Millisecond;
571                                                 string timezone = "";
572                                                 dateTimeDesc.SetDateTime (connection.Session,
573                                                         connection.ErrorHandle,
574                                                         year, month, day, hour, min, sec, fsec,
575                                                         timezone);
576                                         }
577                                         break;
578                                 case OciDataType.Integer:
579                                 case OciDataType.Float:
580                                 case OciDataType.Number:
581                                         bindType = OciDataType.String;
582                                         indicator = 0;
583                                         svalue = "\0";
584                                         // define size for binding
585                                         bindSize = 30; // a NUMBER is 22 bytes but as a string we need more
586                                         // allocate memory
587                                         bytes = new byte [bindSize];
588                                         // convert value from managed type to type to marshal
589                                         if (direction == ParameterDirection.Input || 
590                                                 direction == ParameterDirection.InputOutput) {
591
592                                                 svalue = null;
593                                                 if(v is IFormattable)
594                                                         svalue = ((IFormattable)v).ToString (null, con.SessionFormatProvider);
595                                                 else if (v is OracleNumber)
596                                                         svalue = ((OracleNumber)v).ToString(con.SessionFormatProvider);
597                                                 else
598                                                         svalue = v.ToString();
599
600                                                 svalue = svalue + "\0";
601
602                                                 rsize = 0;
603                                                 // Get size of buffer
604                                                 OciCalls.OCIUnicodeToCharSet (statement.Parent, null, svalue, out rsize);
605
606                                                 // Fill buffer
607                                                 bytes = new byte [bindSize];
608                                                 OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, svalue, out rsize);
609                                         } 
610                                         break;
611                                 case OciDataType.Long:
612                                 case OciDataType.LongVarChar:
613                                         bindType = OciDataType.LongVarChar;
614
615                                         // FIXME: use piecewise fetching for Long, Clob, Blob, and Long Raw
616                                         // See http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14250/oci05bnd.htm#sthref724
617                                         
618                                         bindSize = Size + 5; // 4 bytes prepended for length, bytes, 1 byte NUL character
619
620                                         indicator = 0;
621                                         svalue = "\0";
622                                         // convert value from managed type to type to marshal
623                                         if (direction == ParameterDirection.Input || 
624                                                 direction == ParameterDirection.InputOutput) {
625
626                                                 svalue = v.ToString () + '\0';
627                                         }
628
629                                         bytes = new byte [bindSize];
630                                         // LONG is only ANSI 
631                                         ASCIIEncoding enc = new ASCIIEncoding ();
632                                         
633                                         if (direction == ParameterDirection.Input || 
634                                                 direction == ParameterDirection.InputOutput) {
635                                                 int byteCount = 0;
636                                                 if (svalue.Length > 0) {        
637                                                         byteCount = enc.GetBytes (svalue, 4, svalue.Length, bytes, 0);
638                                                         // LONG VARCHAR prepends a 4-byte length
639                                                         if (byteCount > 0) {
640                                                                 byte[] byteArrayLen = BitConverter.GetBytes ((uint) byteCount);
641                                                                 bytes[0] = byteArrayLen[0];
642                                                                 bytes[1] = byteArrayLen[1];
643                                                                 bytes[2] = byteArrayLen[2];
644                                                                 bytes[3] = byteArrayLen[3];
645                                                         }
646                                                 }
647                                         }
648                                         break;
649                                 case OciDataType.Clob:
650                                         if (direction == ParameterDirection.Input) {
651                                                 svalue = v.ToString();
652                                                 rsize = 0;
653
654                                                 // Get size of buffer
655                                                 OciCalls.OCIUnicodeToCharSet (statement.Parent, null, svalue, out rsize);
656
657                                                 // Fill buffer
658                                                 bytes = new byte[rsize];
659                                                 OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, svalue, out rsize);
660
661                                                 bindType = OciDataType.Long;
662                                                 bindSize = bytes.Length;
663                                         } 
664                                         else if (direction == ParameterDirection.InputOutput) {
665                                                 // not the exact error that .net 2.0 throws, but this is better
666                                                 throw new NotImplementedException ("Parameters of OracleType.Clob with direction of InputOutput are not supported.");
667                                         }
668                                         else {
669                                                 // Output and Return parameters
670                                                 bindSize = -1;
671                                                 lobLocator = (OciLobLocator) connection.Environment.Allocate (OciHandleType.LobLocator);
672                                                 if (lobLocator == null) {
673                                                         OciErrorInfo info = connection.ErrorHandle.HandleError ();
674                                                         throw new OracleException (info.ErrorCode, info.ErrorMessage);
675                                                 }
676                                                 bindOutValue = lobLocator.Handle;
677                                                 bindValue = lobLocator.Handle;
678                                                 lobLocator.ErrorHandle = connection.ErrorHandle;
679                                                 lobLocator.Service = statement.Service;
680                                                 useRef = true;
681                                         }
682                                         break;
683                                 case OciDataType.Blob:
684                                         if (direction == ParameterDirection.Input) {
685                                                 if (v is byte[]) {
686                                                         bytes = (byte[]) v;
687                                                         bindType = OciDataType.LongRaw;
688                                                         bindSize = bytes.Length;
689                                                 }
690                                                 else if (v is OracleLob) {
691                                                         OracleLob lob = (OracleLob) v;
692                                                         if (lob.LobType == OracleType.Blob) {
693                                                                 lobLocator = lob.Locator;
694                                                                 bindOutValue = lobLocator.Handle;
695                                                                 bindValue = lobLocator.Handle;
696                                                                 lobLocator.ErrorHandle = connection.ErrorHandle;
697                                                                 lobLocator.Service = connection.ServiceContext;
698                                                                 useRef = true;
699                                                         }
700                                                         else
701                                                                 throw new NotImplementedException("For OracleType.Blob, data type OracleLob of LobType Clob/NClob is not implemented.");
702                                                 }
703                                                 else
704                                                         throw new NotImplementedException ("For OracleType.Blob, data type not implemented: " + v.GetType().ToString()); // ?
705                                         }
706                                         else if (direction == ParameterDirection.InputOutput) {
707                                                 // not the exact error that .net 2.0 throws, but this is better
708                                                 throw new NotImplementedException ("Parameters of OracleType.Blob with direction of InputOutput are not supported.");
709                                         }
710                                         else {
711                                                 bindSize = -1;
712                                                 if (value != null && value is OracleLob) {
713                                                         OracleLob blob = (OracleLob) value;
714                                                         if (blob.LobType == OracleType.Blob)
715                                                                 if (value != OracleLob.Null) {
716                                                                         lobLocator = blob.Locator;
717                                                                         byte[] bs = (byte[]) blob.Value;
718                                                                         bindSize = bs.Length;
719                                                                 }
720                                                 }
721                                                 if (lobLocator == null) {
722                                                         lobLocator = (OciLobLocator) connection.Environment.Allocate (OciHandleType.LobLocator);
723                                                         if (lobLocator == null) {
724                                                                 OciErrorInfo info = connection.ErrorHandle.HandleError ();
725                                                                 throw new OracleException (info.ErrorCode, info.ErrorMessage);
726                                                         }
727                                                 }
728                                                 bindOutValue = lobLocator.Handle;
729                                                 bindValue = lobLocator.Handle;
730                                                 lobLocator.ErrorHandle = connection.ErrorHandle;
731                                                 lobLocator.Service = connection.ServiceContext;
732                                                 useRef = true;
733                                         }
734                                         break;
735                                 default:
736                                         // FIXME: move this up - see how Char, Number, and Date are done...
737                                         if (direction == ParameterDirection.Output || 
738                                                 direction == ParameterDirection.InputOutput || 
739                                                 direction == ParameterDirection.ReturnValue) {
740
741                                                 switch(ociType) {
742                                                 case OciDataType.RowIdDescriptor:
743                                                         size = 10;
744                                                         bindType = OciDataType.Char;
745                                                         bindSize = size * 2;
746                                                         bindOutValue = OciCalls.AllocateClear (bindSize);
747                                                         bindValue = bindOutValue;
748                                                         break;
749                                                 case OciDataType.RSet: // REF CURSOR
750                                                         cursor = IntPtr.Zero;
751                                                         OciCalls.OCIHandleAlloc (connection.Environment,
752                                                                 out cursor,
753                                                                 OciHandleType.Statement,
754                                                                 0,
755                                                                 IntPtr.Zero);
756
757                                                         bindSize = 0;
758                                                         bindType = OciDataType.RSet;
759                                                         break;
760                                                 default:
761                                                         // define other types
762                                                         throw new NotImplementedException ("Data Type not implemented: " + ociType.ToString() + ".");
763                                                 } // switch of ociDataType for output
764                                                 bindValue = bindOutValue;
765                                         }
766                                         else if ((v == DBNull.Value || v == null || isnull == true) && direction == ParameterDirection.Input) {
767                                                 indicator = 0;
768                                                 bindType = OciDataType.VarChar2;
769                                                 bindSize = 0;
770                                         }
771                                         else {
772                                                 if (bindOracleType == OracleType.Raw) {
773                                                         byte[] val = v as byte[];
774                                                         bindValue = OciCalls.AllocateClear (val.Length);
775                                                         Marshal.Copy (val, 0, bindValue, val.Length);
776                                                         bindSize = val.Length;
777                                                 } else {
778                                                         svalue = v.ToString () + '\0';
779                                                         rsize = 0;
780
781                                                         // Get size of buffer
782                                                         OciCalls.OCIUnicodeToCharSet (statement.Parent, null, svalue, out rsize);
783
784                                                         // Fill buffer
785                                                         bytes = new byte[rsize];
786                                                         OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, svalue, out rsize);
787
788                                                         bindType = OciDataType.String;
789                                                         bindSize = bytes.Length;
790                                                 } // else oracleType
791                                         } // else - Input, Ouput...
792                                         break;
793                                 }                       
794                         }
795                         
796                         // Now, call the appropriate OCI Bind function;
797
798                         if (useRef == true) {
799                                 if (bindType == OciDataType.TimeStamp) {
800                                         bindValue = dateTimeDesc.Handle;
801                                         status = OciCalls.OCIBindByNameRef (statement,
802                                                 out tmpHandle,
803                                                 connection.ErrorHandle,
804                                                 ParameterName,
805                                                 ParameterName.Length,
806                                                 ref bindValue,
807                                                 bindSize,
808                                                 bindType,
809                                                 ref indicator,
810                                                 IntPtr.Zero,
811                                                 IntPtr.Zero,
812                                                 0,
813                                                 IntPtr.Zero,
814                                                 0);
815                                 }
816                                 else {
817                                         status = OciCalls.OCIBindByNameRef (statement,
818                                                 out tmpHandle,
819                                                 connection.ErrorHandle,
820                                                 ParameterName,
821                                                 ParameterName.Length,
822                                                 ref bindValue,
823                                                 bindSize,
824                                                 bindType,
825                                                 ref indicator,
826                                                 IntPtr.Zero,
827                                                 IntPtr.Zero,
828                                                 0,
829                                                 IntPtr.Zero,
830                                                 0);
831                                 }
832                         }
833                         else if (bindType == OciDataType.RSet) {
834                                 status = OciCalls.OCIBindByNameRef (statement,
835                                         out tmpHandle,
836                                         connection.ErrorHandle,
837                                         ParameterName,
838                                         ParameterName.Length,
839                                         ref cursor,
840                                         bindSize,
841                                         bindType,
842                                         ref indicator,
843                                         IntPtr.Zero,
844                                         IntPtr.Zero,
845                                         0,
846                                         IntPtr.Zero,
847                                         0);
848                         }
849                         else if (bytes != null) {
850                                 status = OciCalls.OCIBindByNameBytes (statement,
851                                         out tmpHandle,
852                                         connection.ErrorHandle,
853                                         ParameterName,
854                                         ParameterName.Length,
855                                         bytes,
856                                         bindSize,
857                                         bindType,
858                                         ref indicator,
859                                         IntPtr.Zero,
860                                         IntPtr.Zero,
861                                         0,
862                                         IntPtr.Zero,
863                                         0);
864                         }
865                         else {
866                                 status = OciCalls.OCIBindByName (statement,
867                                         out tmpHandle,
868                                         connection.ErrorHandle,
869                                         ParameterName,
870                                         ParameterName.Length, // FIXME: this should be in bytes!
871                                         bindValue,
872                                         bindSize,
873                                         bindType,
874                                         ref indicator,
875                                         IntPtr.Zero,
876                                         IntPtr.Zero,
877                                         0,
878                                         IntPtr.Zero,
879                                         0);
880                         }
881                         OciErrorHandle.ThrowExceptionIfError (connection.ErrorHandle, status);
882
883                         bindHandle.SetHandle (tmpHandle);
884                 }
885
886                 object ICloneable.Clone ()
887                 {
888                         return new OracleParameter(this);
889                 }
890
891                 private void InferOracleType (object value)
892                 {
893                         // Should we throw an exception here?
894                         if (value == null || value == DBNull.Value)
895                                 return;
896                         
897                         Type type = value.GetType ();
898                         string exception = String.Format ("The parameter data type of {0} is invalid.", type.FullName);
899                         switch (type.FullName) {
900                         case "System.Int64":
901                                 SetOracleType (OracleType.Number, true);
902                                 break;
903                         case "System.Boolean":
904                         case "System.Byte":
905                                 SetOracleType (OracleType.Byte, true);
906                                 break;
907                         case "System.String":
908                         case "System.Data.OracleClient.OracleString":
909                                 SetOracleType (OracleType.VarChar, true);
910                                 break;
911                         case "System.Data.OracleClient.OracleDateTime":
912                         case "System.DateTime":
913                                 SetOracleType (OracleType.DateTime, true);
914                                 break;
915                         case "System.Decimal":
916                         case "System.Data.OracleClient.OracleNumber":
917                                 SetOracleType (OracleType.Number, true);
918                                 break;
919                         case "System.Double":
920                                 SetOracleType (OracleType.Double, true);
921                                 break;
922                         case "System.Byte[]":
923                         case "System.Guid":
924                                 SetOracleType (OracleType.Raw, true);
925                                 break;
926                         case "System.Int32":
927                                 SetOracleType (OracleType.Int32, true);
928                                 break;
929                         case "System.Single":
930                                 SetOracleType (OracleType.Float, true);
931                                 break;
932                         case "System.Int16":
933                                 SetOracleType (OracleType.Int16, true);
934                                 break;
935                         case "System.DBNull":
936                                 break; //unable to guess type
937                         case "System.Data.OracleClient.OracleLob":
938                                 SetOracleType (((OracleLob) value).LobType, true); 
939                                 break;
940                         default:
941                                 throw new ArgumentException (exception);
942                         }
943                 }
944
945                 private int InferSize ()
946                 {
947                         int newSize = 0;
948
949                         switch (ociType) {
950                         case OciDataType.VarChar2:
951                         case OciDataType.String:
952                         case OciDataType.VarChar:
953                         case OciDataType.Char:
954                         case OciDataType.CharZ:
955                         case OciDataType.OciString:
956                         case OciDataType.Long:
957                         case OciDataType.LongVarChar:
958                                 if (value == null || value == DBNull.Value)
959                                         newSize = 0;
960                                 else
961                                         newSize = value.ToString ().Length;
962                                 break;
963                         case OciDataType.RowIdDescriptor:
964                                 newSize = 10;
965                                 break;
966                         case OciDataType.Integer:
967                         case OciDataType.Number:
968                         case OciDataType.Float:
969                                 newSize = 22;
970                                 break;
971                         case OciDataType.Date:
972                                 newSize = 7;
973                                 break;
974                         case OciDataType.TimeStamp:
975                                 newSize = 11;
976                                 break;
977                         case OciDataType.Blob:
978                         case OciDataType.Clob:
979                         case OciDataType.RSet: // REF CURSOR
980                                 newSize = -1;
981                                 break;
982                         default:
983                                 if (value == null || value == DBNull.Value)
984                                         newSize = 0;
985                                 else
986                                         newSize = value.ToString ().Length;
987                                 break;
988                         }
989
990                         sizeSet = true;
991
992                         return newSize;
993                 }
994
995                 private void SetDbType (DbType type)
996                 {
997                         string exception = String.Format ("No mapping exists from DbType {0} to a known OracleType.", type);
998                         switch (type) {
999                         case DbType.AnsiString:
1000                                 oracleType = OracleType.VarChar;
1001                                 ociType = OciDataType.VarChar;
1002                                 break;
1003                         case DbType.AnsiStringFixedLength:
1004                                 oracleType = OracleType.Char;
1005                                 ociType = OciDataType.Char;
1006                                 break;
1007                         case DbType.Binary:
1008                         case DbType.Guid:
1009                                 oracleType = OracleType.Raw;
1010                                 ociType = OciDataType.Raw;
1011                                 break;
1012                         case DbType.Boolean:
1013                         case DbType.Byte:
1014                                 oracleType = OracleType.Byte;
1015                                 ociType = OciDataType.Integer;
1016                                 break;
1017                         case DbType.Currency:
1018                         case DbType.Decimal:
1019                         case DbType.Int64:
1020                                 oracleType = OracleType.Number;
1021                                 ociType = OciDataType.Number;
1022                                 break;
1023                         case DbType.Date:
1024                         case DbType.DateTime:
1025                         case DbType.Time:
1026                                 oracleType = OracleType.DateTime;
1027                                 ociType = OciDataType.Char;
1028                                 break;
1029                         case DbType.Double:
1030                                 oracleType = OracleType.Double;
1031                                 ociType = OciDataType.Float;
1032                                 break;
1033                         case DbType.Int16:
1034                                 oracleType = OracleType.Int16;
1035                                 ociType = OciDataType.Integer;
1036                                 break;
1037                         case DbType.Int32:
1038                                 oracleType = OracleType.Int32;
1039                                 ociType = OciDataType.Integer;
1040                                 break;
1041                         case DbType.Object:
1042                                 oracleType = OracleType.Blob;
1043                                 ociType = OciDataType.Blob;
1044                                 break;
1045                         case DbType.Single:
1046                                 oracleType = OracleType.Float;
1047                                 ociType = OciDataType.Float;
1048                                 break;
1049                         case DbType.String:
1050                                 oracleType = OracleType.NVarChar;
1051                                 ociType = OciDataType.VarChar;
1052                                 break;
1053                         case DbType.StringFixedLength:
1054                                 oracleType = OracleType.NChar;
1055                                 ociType = OciDataType.Char;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
1056
1057                                 break;
1058                         default:
1059                                 throw new ArgumentException (exception);
1060                         }
1061                         dbType = type;
1062                 }
1063
1064                 private void SetOracleType (OracleType type, bool inferring)
1065                 {
1066                         FreeHandle ();
1067                         string exception = String.Format ("No mapping exists from OracleType {0} to a known DbType.", type);
1068                         switch (type) {
1069                         case OracleType.BFile:
1070                         case OracleType.Blob:
1071                                 dbType = DbType.Binary;
1072                                 ociType = OciDataType.Blob;
1073                                 break;
1074                         case OracleType.LongRaw:
1075                         case OracleType.Raw:
1076                                 dbType = DbType.Binary;
1077                                 ociType = OciDataType.Raw;
1078                                 break;
1079                         case OracleType.Byte:
1080                                 dbType = DbType.Byte;
1081                                 ociType = OciDataType.Number;
1082                                 break;
1083                         case OracleType.Char:
1084                                 dbType = DbType.AnsiString;
1085                                 ociType = OciDataType.Char;
1086                                 break;
1087                         case OracleType.Clob:
1088                                 dbType = DbType.AnsiString;
1089                                 ociType = OciDataType.Clob;
1090                                 break;
1091                         case OracleType.LongVarChar:
1092                         case OracleType.RowId:
1093                         case OracleType.VarChar:
1094                                 dbType = DbType.AnsiString;
1095                                 ociType = OciDataType.VarChar;
1096                                 break;
1097                         case OracleType.Cursor: // REF CURSOR
1098                                 ociType = OciDataType.RSet;
1099                                 dbType = DbType.Object;
1100                                 break;
1101                         case OracleType.IntervalDayToSecond:
1102                                 dbType = DbType.AnsiStringFixedLength;
1103                                 ociType = OciDataType.Char;
1104                                 break;
1105                         case OracleType.Timestamp:
1106                         case OracleType.TimestampLocal:
1107                         case OracleType.TimestampWithTZ:
1108                                 dbType = DbType.DateTime;
1109                                 ociType = OciDataType.TimeStamp;
1110                                 break;
1111                         case OracleType.DateTime:
1112                                 dbType = DbType.DateTime;
1113                                 ociType = OciDataType.Date;
1114                                 break;
1115                         case OracleType.Double:
1116                                 dbType = DbType.Double;
1117                                 ociType = OciDataType.Number;
1118                                 break;
1119                         case OracleType.Float:
1120                                 dbType = DbType.Single;
1121                                 ociType = OciDataType.Number;
1122                                 break;
1123                         case OracleType.Int16:
1124                                 dbType = DbType.Int16;
1125                                 ociType = OciDataType.Number;
1126                                 break;
1127                         case OracleType.Int32:
1128                         case OracleType.IntervalYearToMonth:
1129                                 dbType = DbType.Int32;
1130                                 ociType = OciDataType.Number;
1131                                 break;
1132                         case OracleType.NChar:
1133                                 dbType = DbType.StringFixedLength;
1134                                 ociType = OciDataType.Char;
1135                                 break;
1136                         case OracleType.NClob:
1137                         case OracleType.NVarChar:
1138                                 dbType = DbType.String;
1139                                 ociType = OciDataType.Char;
1140                                 break;
1141                         case OracleType.Number:
1142                                 dbType = DbType.VarNumeric;
1143                                 ociType = OciDataType.Number;
1144                                 break;
1145                         case OracleType.SByte:
1146                                 dbType = DbType.SByte;
1147                                 ociType = OciDataType.Number;
1148                                 break;
1149                         case OracleType.UInt16:
1150                                 dbType = DbType.UInt16;
1151                                 ociType = OciDataType.Number;
1152                                 break;
1153                         case OracleType.UInt32:
1154                                 dbType = DbType.UInt32;
1155                                 ociType = OciDataType.Number;
1156                                 break;
1157                         default:
1158                                 throw new ArgumentException (exception);
1159                         }
1160
1161                         if (!oracleTypeSet || !inferring )
1162                                 oracleType = type;
1163                         bindOracleType = type;
1164                 }
1165
1166 #if NET_2_0
1167                 public override void ResetDbType ()
1168                 {
1169                         ResetOracleType ();
1170                 }
1171
1172                 public void ResetOracleType ()
1173                 {
1174                         oracleTypeSet = false;
1175                         InferOracleType (value);
1176                 }
1177 #endif // NET_2_0
1178
1179                 public override string ToString ()
1180                 {
1181                         return ParameterName;
1182                 }
1183
1184                 private void GetOutValue (OracleCommand cmd)
1185                 {
1186                         // used to update the parameter value
1187                         // for Output, the output of InputOutput, and Return parameters
1188                         value = DBNull.Value;
1189                         if (indicator == -1)
1190                                 return;
1191
1192                         int rsize = 0;
1193                         IntPtr env = IntPtr.Zero;
1194                         StringBuilder ret = null;
1195
1196                         // FIXME: redo all types - see how Char, Number, and Date are done
1197                         // here and in Bind()
1198
1199                         switch (ociType) {
1200                         case OciDataType.VarChar2:
1201                         case OciDataType.String:
1202                         case OciDataType.VarChar:
1203                         case OciDataType.Char:
1204                         case OciDataType.CharZ:
1205                         case OciDataType.OciString:
1206                         case OciDataType.RowIdDescriptor:
1207                                 // Get length of returned string
1208                                 rsize = 0;
1209                                 env = cmd.Connection.Environment;
1210                                 OciCalls.OCICharSetToUnicode (env, null, bytes, out rsize);
1211
1212                                 // Get string
1213                                 ret = new StringBuilder(rsize);
1214                                 OciCalls.OCICharSetToUnicode (env, ret, bytes, out rsize);
1215
1216                                 value = ret.ToString ();
1217                                 break;
1218                         case OciDataType.Long:
1219                         case OciDataType.LongVarChar:
1220                                 int longSize = 0;
1221                                 if (BitConverter.IsLittleEndian)
1222                                         longSize = BitConverter.ToInt32 (new byte [] {bytes [0], bytes [1], bytes [2], bytes [3]}, 0);
1223                                 else
1224                                         longSize = BitConverter.ToInt32 (new byte [] {bytes [3], bytes [2], bytes [1], bytes [0]}, 0);
1225
1226                                 ASCIIEncoding encoding = new ASCIIEncoding ();
1227                                 value = encoding.GetString (bytes, 4, longSize);
1228                                 encoding = null;
1229                                 break;
1230                         case OciDataType.Integer:
1231                         case OciDataType.Number:
1232                         case OciDataType.Float:
1233                                 rsize = 0;
1234                                 env = cmd.Connection.Environment;
1235                                 OciCalls.OCICharSetToUnicode (env, null, bytes, out rsize);
1236
1237                                 // Get string
1238                                 ret = new StringBuilder(rsize);
1239                                 OciCalls.OCICharSetToUnicode (env, ret, bytes, out rsize);
1240
1241                                 // if not empty, parse string as a decimal using session format
1242                                 if (ret.Length > 0)
1243                                         value = Decimal.Parse (ret.ToString (), cmd.Connection.SessionFormatProvider);
1244                                 break;
1245                         case OciDataType.TimeStamp:
1246                                 value = dateTimeDesc.GetDateTime (connection.Environment, dateTimeDesc.ErrorHandle);
1247                                 break;
1248                         case OciDataType.Date:
1249                                 value = UnpackDate (bytes);
1250                                 break;
1251                         case OciDataType.Blob:
1252                         case OciDataType.Clob:
1253                                 if (value != null && value is OracleLob && value != OracleLob.Null) {
1254                                         OracleLob lob2 = (OracleLob) value;
1255                                         lob2.connection = connection;
1256                                 }
1257                                 else {
1258                                         OracleLob lob = new OracleLob (lobLocator, ociType);
1259                                         lob.connection = connection;
1260                                         value = lob;
1261                                 }
1262                                 break;
1263                         case OciDataType.RSet: // REF CURSOR                            
1264                                 OciStatementHandle cursorStatement = GetOutRefCursor (cmd);
1265                                 value = new OracleDataReader (cursorStatement.Command, cursorStatement, true, CommandBehavior.Default);
1266                                 break;
1267                         default:
1268                                 throw new NotImplementedException ("Data Type not implemented: " + ociType.ToString() + ".");
1269                         }
1270                 }
1271
1272                 internal OciStatementHandle GetOutRefCursor (OracleCommand cmd) 
1273                 {
1274                                 OciStatementHandle cursorStatement = new OciStatementHandle (cmd.Connection.ServiceContext, cursor);
1275
1276                                 cursorStatement.ErrorHandle = cmd.ErrorHandle;
1277                                 cursorStatement.Command = cmd;
1278                                 cursorStatement.SetupRefCursorResult (cmd.Connection);
1279                                 cursorStatement.Service = cmd.Connection.ServiceContext;
1280                                 cursor = IntPtr.Zero;
1281                                 return cursorStatement;                 
1282                 }
1283
1284                 internal void Update (OracleCommand cmd)
1285                 {
1286                         if (Direction != ParameterDirection.Input)
1287                                 GetOutValue (cmd);
1288
1289                         FreeHandle ();
1290                 }
1291
1292                 internal void FreeHandle ()
1293                 {
1294                         switch (ociType) {
1295                         case OciDataType.Clob:
1296                         case OciDataType.Blob:
1297                                 lobLocator = null;
1298                                 break;
1299                         case OciDataType.Raw:
1300                                 Marshal.FreeHGlobal (bindValue);
1301                                 break;
1302                         case OciDataType.TimeStamp:
1303                                 break;
1304                         default:
1305                                 Marshal.FreeHGlobal (bindOutValue);
1306                                 break;
1307                         }
1308
1309                         bindOutValue = IntPtr.Zero;
1310                         bindValue = IntPtr.Zero;
1311
1312                         bindHandle = null;
1313                         connection = null;
1314                 }
1315
1316                 // copied from OciDefineHandle
1317                 [MonoTODO ("Be able to handle negative dates... i.e. BCE.")]
1318                 private DateTime UnpackDate (byte[] bytes)
1319                 {
1320                         byte century = bytes [0];
1321                         byte year    = bytes [1];
1322                         byte month   = bytes [2];
1323                         byte day     = bytes [3];
1324                         byte hour    = bytes [4];
1325                         byte minute  = bytes [5];
1326                         byte second  = bytes [6];
1327
1328
1329                         return new DateTime ((century - 100) * 100 + (year - 100),
1330                                                 month,
1331                                                 day,
1332                                                 hour - 1,
1333                                                 minute - 1,
1334                                                 second - 1);
1335
1336                 }
1337
1338                 private byte[] PackDate (DateTime dateValue)
1339                 {
1340                         byte[] buffer = new byte[7];
1341
1342                         buffer[0] = (byte)((dateValue.Year / 100) + 100); //century
1343                         buffer[1] = (byte)((dateValue.Year % 100) + 100); // Year
1344                         buffer[2] = (byte)dateValue.Month;
1345                         buffer[3] = (byte)dateValue.Day;
1346                         buffer[4] = (byte)(dateValue.Hour+1);
1347                         buffer[5] = (byte)(dateValue.Minute+1);
1348                         buffer[6] = (byte)(dateValue.Second+1);
1349
1350                         return buffer;
1351                 }
1352
1353                 #endregion // Methods
1354
1355                 internal sealed class OracleParameterConverter : ExpandableObjectConverter
1356                 {
1357                         public OracleParameterConverter ()
1358                         {
1359                         }
1360
1361                         [MonoTODO]
1362                         public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
1363                         {
1364                                 throw new NotImplementedException ();
1365                         }
1366
1367                         [MonoTODO]
1368                         public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
1369                         {
1370                                 throw new NotImplementedException ();
1371                         }
1372                 }
1373         }
1374 }