2005-09-08 Peter Dennis Bartok <pbartok@novell.com>
[mono.git] / mcs / class / System.Data.OracleClient / System.Data.OracleClient / OracleParameter.cs
index 32c116bf4bf8a8aecf08a3a97cb411fc2a96cbab..d7fe97650dc592d074f67d3ee3a332bea0153a26 100644 (file)
@@ -9,8 +9,12 @@
 //
 // Authors: 
 //    Tim Coleman <tim@timcoleman.com>
+//    Daniel Moragn <danielmorgan@verizon.net>
+//    Hubert FONGARNAND <informatique.internet@fiducial.fr>
 //
 // Copyright (C) Tim Coleman , 2003
+// Copyright (C) Daniel Morgan, 2005
+// Copyright (C) Hubert FONGARNAND, 2005
 //
 // Licensed under the MIT/X11 License.
 //
@@ -22,6 +26,7 @@ using System.Data;
 using System.Data.OracleClient.Oci;
 using System.Globalization;
 using System.Runtime.InteropServices;
+using System.Text;
 
 namespace System.Data.OracleClient {
        [TypeConverter (typeof(OracleParameter.OracleParameterConverter))]
@@ -42,15 +47,44 @@ namespace System.Data.OracleClient {
                DbType dbType = DbType.AnsiString;
                int offset = 0;
                bool sizeSet = false;
-               object value = null;
+               object value = DBNull.Value;
+               OciLobLocator lobLocator = null;  // only if Blob or Clob
+               IntPtr bindOutValue = IntPtr.Zero;
+               OciDateTimeDescriptor dateTimeDesc = null;
 
                OracleParameterCollection container = null;
                OciBindHandle bindHandle;
+               OciErrorHandle errorHandle;
+               OracleConnection connection;
+               byte[] bytes = null;
+               IntPtr bindValue = IntPtr.Zero;
+
+               short indicator = 0; // TODO: handle indicator to indicate NULL value for OUT parameters
+               int bindSize = 0;
 
                #endregion // Fields
 
                #region Constructors
 
+               // constructor for cloning the object
+               internal OracleParameter (OracleParameter value) {
+                       this.name = value.name;
+                       this.oracleType = value.oracleType;
+                       this.ociType = value.ociType;
+                       this.size = value.size;
+                       this.direction = value.direction;
+                       this.isNullable = value.isNullable;
+                       this.precision = value.precision;
+                       this.scale = value.scale;
+                       this.srcColumn = value.srcColumn;
+                       this.srcVersion = value.srcVersion;
+                       this.dbType = value.dbType;
+                       this.offset = value.offset;
+                       this.sizeSet = value.sizeSet;
+                       this.value = value.value;
+                       this.lobLocator = value.lobLocator;
+               }
+
                public OracleParameter ()
                        : this (String.Empty, OracleType.VarChar, 0, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
                {
@@ -171,7 +205,7 @@ namespace System.Data.OracleClient {
                        set { srcColumn = value; }
                }
 
-               [DefaultValue (DataRowVersion.Current)]
+               [DefaultValue ("Current")]
                public DataRowVersion SourceVersion {
                        get { return srcVersion; }
                        set { srcVersion = value; }
@@ -191,12 +225,25 @@ namespace System.Data.OracleClient {
 
                private void AssertSizeIsSet ()
                {
-                       if (!sizeSet)
-                               throw new Exception ("Size must be set.");
+                       switch (ociType) {
+                       case OciDataType.VarChar2:
+                       case OciDataType.String:
+                       case OciDataType.VarChar:
+                       case OciDataType.Char:
+                       case OciDataType.CharZ:
+                       case OciDataType.OciString:
+                               if (!sizeSet)
+                                       throw new Exception ("Size must be set.");
+                               break;
+                       default:
+                               break;
+                       }
                }
 
-               internal void Bind (OciStatementHandle statement, OracleConnection connection)
+               internal void Bind (OciStatementHandle statement, OracleConnection connection) 
                {
+                       errorHandle = connection.ErrorHandle;
+
                        if (bindHandle == null)
                                bindHandle = new OciBindHandle ((OciHandle) statement);
 
@@ -207,34 +254,243 @@ namespace System.Data.OracleClient {
                        if (!sizeSet)
                                size = InferSize ();
 
+                       bindSize = size;
+                       object v = value;
                        int status = 0;
-                       int indicator = 0;
                        OciDataType bindType = ociType;
-                       IntPtr bindValue = IntPtr.Zero;
-                       int bindSize = size;
-
-                       if (value == DBNull.Value)
-                               indicator = -1;
+                       int rsize = 0;
+
+                       // TODO: handle InputOutput and Return parameters
+                       if (direction == ParameterDirection.Output) {
+                               // TODO: need to figure out how OracleParameter
+                               //       which uses OciBindHandle to share code
+                               //       with OciDefineHandle
+                               switch(ociType) {
+                                       case OciDataType.VarChar2:
+                                       case OciDataType.String:
+                                       case OciDataType.VarChar:
+                                       case OciDataType.Char:
+                                       case OciDataType.CharZ:
+                                       case OciDataType.OciString:
+                                               bindType = OciDataType.Char;
+                                               bindSize = size * 2;
+                                               bindOutValue = Marshal.AllocHGlobal (bindSize);
+                                               break;
+                                       case OciDataType.RowIdDescriptor:
+                                               size = 10;
+                                               bindType = OciDataType.Char;
+                                               bindSize = size * 2;
+                                               bindOutValue = Marshal.AllocHGlobal (bindSize);
+                                               break;
+                                       case OciDataType.Date:
+                                               bindSize = 7;
+                                               bindType = OciDataType.Date;
+                                               bindOutValue = Marshal.AllocHGlobal (bindSize);
+                                               break;
+                                       case OciDataType.TimeStamp:
+                                               dateTimeDesc = (OciDateTimeDescriptor) connection.Environment.Allocate (OciHandleType.TimeStamp);
+                                               if (dateTimeDesc == null) {
+                                                       OciErrorInfo info = connection.ErrorHandle.HandleError ();
+                                                       throw new OracleException (info.ErrorCode, info.ErrorMessage);
+                                               }
+                                               dateTimeDesc.ErrorHandle = connection.ErrorHandle;
+                                               bindSize = 11;
+                                               bindType = OciDataType.TimeStamp;
+                                               bindOutValue = dateTimeDesc.Handle;
+                                               break;
+                                       case OciDataType.Number:
+                                               bindSize = 22;
+                                               bindType = OciDataType.Char;
+                                               bindOutValue = Marshal.AllocHGlobal (bindSize);
+                                               break;
+                                       case OciDataType.Long:
+                                       case OciDataType.LongVarChar:
+                                               // LAMESPEC: you don't know size until you get it;
+                                               // therefore, you must allocate an insane size
+                                               // see OciDefineHandle
+                                               bindSize = OciDefineHandle.LongVarCharMaxValue;
+                                               bindOutValue = Marshal.AllocHGlobal (bindSize);
+                                               bindType = OciDataType.LongVarChar;
+                                               break;
+                                       case OciDataType.Blob:
+                                       case OciDataType.Clob:
+                                               bindSize = -1;
+                                               lobLocator = (OciLobLocator) connection.Environment.Allocate (OciHandleType.LobLocator);
+                                               if (lobLocator == null) {
+                                                       OciErrorInfo info = connection.ErrorHandle.HandleError ();
+                                                       throw new OracleException (info.ErrorCode, info.ErrorMessage);
+                                               }
+                                               bindOutValue = lobLocator.Handle;
+                                               lobLocator.ErrorHandle = connection.ErrorHandle;
+                                               lobLocator.Service = statement.Service;
+                                               break;
+                                       default:
+                                               // define other types
+                                               throw new NotImplementedException ();
+                               }
+                               bindValue = bindOutValue;
+                       }
+                       else if (v == DBNull.Value || v == null) {
+                               indicator = 0;
+                               bindType = OciDataType.VarChar2;
+                               bindSize = 0;
+                       }
                        else {
-                               bindType = OciDataType.VarChar2; // FIXME
-                               bindValue = Marshal.StringToHGlobalAnsi (value.ToString ());
-                               bindSize = value.ToString ().Length;
+                               // TODO: do other data types and oracle data types
+                               // should I be using IConvertible to convert?
+                               string sDate = "";
+                               DateTime dt = DateTime.MinValue;
+                               if (oracleType == OracleType.Timestamp){
+                                       bindType = OciDataType.TimeStamp;
+                                       bindSize = 11;
+                                       dt = DateTime.MinValue;
+                                       sDate = "";
+                                       if (v is String){
+                                               sDate = (string) v;
+                                               dt = DateTime.Parse (sDate);
+                                       }
+                                       else if (v is DateTime)
+                                               dt = (DateTime) v;
+                                       else if (v is OracleString){
+                                               sDate = (string) v;
+                                               dt = DateTime.Parse (sDate);
+                                       }
+                                       else
+                                               throw new NotImplementedException (); // ?
+                                       
+                                       short year = (short) dt.Year;
+                                       byte month = (byte) dt.Month;
+                                       byte day = (byte) dt.Day;
+                                       byte hour = (byte) dt.Hour;
+                                       byte min = (byte) dt.Minute;
+                                       byte sec = (byte) dt.Second;
+                                       uint fsec = (uint) dt.Millisecond;
+                                       string timezone = "";
+                                       dateTimeDesc = (OciDateTimeDescriptor) connection.Environment.Allocate (OciHandleType.TimeStamp);
+                                       if (dateTimeDesc == null) {
+                                               OciErrorInfo info = connection.ErrorHandle.HandleError ();
+                                               throw new OracleException (info.ErrorCode, info.ErrorMessage);
+                                       }
+                                       dateTimeDesc.ErrorHandle = connection.ErrorHandle;
+                                       dateTimeDesc.SetDateTime (connection.Session,
+                                               connection.ErrorHandle, 
+                                               year, month, day, hour, min, sec, fsec,
+                                               timezone);
+                               }
+                               else if (oracleType == OracleType.DateTime) {
+                                       sDate = "";
+                                       dt = DateTime.MinValue;
+                                       if (v is String) {
+                                               sDate = (string) v;
+                                               dt = DateTime.Parse (sDate);
+                                       }
+                                       else if (v is DateTime)
+                                               dt = (DateTime) v;
+                                       else if (v is OracleString) {
+                                               sDate = (string) v;
+                                               dt = DateTime.Parse (sDate);
+                                       }
+                                       else if (v is OracleDateTime) {
+                                               OracleDateTime odt = (OracleDateTime) v;
+                                               dt = (DateTime) odt.Value;
+                                       }
+                                       else
+                                               throw new NotImplementedException (); // ?
+
+                                       bytes = PackDate (dt);
+                                       bindType = OciDataType.Date;
+                                       bindSize = bytes.Length;
+                               }
+                               else if (oracleType == OracleType.Blob) {
+                                       bytes = (byte[]) v;
+                                       bindType = OciDataType.LongRaw;
+                                       bindSize = bytes.Length;
+                               }
+                               else if (oracleType == OracleType.Clob) {
+                                       string sv = (string) v;
+                                       rsize = 0;
+                       
+                                       // Get size of buffer
+                                       OciCalls.OCIUnicodeToCharSet (statement.Parent, null, sv, out rsize);
+                       
+                                       // Fill buffer
+                                       bytes = new byte[rsize];
+                                       OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, sv, out rsize);
+
+                                       bindType = OciDataType.Long;
+                                       bindSize = bytes.Length;
+                               }
+                               else if (oracleType == OracleType.Raw) {
+                                       byte[] val = v as byte[];
+                                       bindValue = Marshal.AllocHGlobal (val.Length);
+                                       Marshal.Copy (val, 0, bindValue, val.Length);
+                                       bindSize = val.Length;
+                               }
+                               else {
+                                       string svalue = v.ToString ();
+                                       rsize = 0;
+                       
+                                       // Get size of buffer
+                                       OciCalls.OCIUnicodeToCharSet (statement.Parent, null, svalue, out rsize);
+                       
+                                       // Fill buffer
+                                       bytes = new byte[rsize];
+                                       OciCalls.OCIUnicodeToCharSet (statement.Parent, bytes, svalue, out rsize);
+
+                                       bindType = OciDataType.VarChar2;
+                                       bindSize = v.ToString ().Length;
+                               }
                        }
 
-                       status = OciCalls.OCIBindByName (statement,
-                                               out tmpHandle,
-                                               connection.ErrorHandle,
-                                               ParameterName,
-                                               ParameterName.Length,
-                                               bindValue,
-                                               bindSize,
-                                               bindType,
-                                               indicator,
-                                               IntPtr.Zero,
-                                               IntPtr.Zero,
-                                               0,
-                                               IntPtr.Zero,
-                                               0);
+                       if (dateTimeDesc != null) {
+                               bindValue = dateTimeDesc.Handle;
+                               status = OciCalls.OCIBindByNameRef (statement,
+                                       out tmpHandle,
+                                       connection.ErrorHandle,
+                                       ParameterName,
+                                       ParameterName.Length,
+                                       ref bindValue,
+                                       bindSize,
+                                       bindType,
+                                       ref indicator,
+                                       IntPtr.Zero,
+                                       IntPtr.Zero,
+                                       0,
+                                       IntPtr.Zero,
+                                       0);
+                       }
+                       else if (bytes != null) {
+                               status = OciCalls.OCIBindByNameBytes (statement,
+                                       out tmpHandle,
+                                       connection.ErrorHandle,
+                                       ParameterName,
+                                       ParameterName.Length,
+                                       bytes,
+                                       bindSize,
+                                       bindType,
+                                       ref indicator,
+                                       IntPtr.Zero,
+                                       IntPtr.Zero,
+                                       0,
+                                       IntPtr.Zero,
+                                       0);
+                       }
+                       else {
+                               status = OciCalls.OCIBindByName (statement,
+                                       out tmpHandle,
+                                       connection.ErrorHandle,
+                                       ParameterName,
+                                       ParameterName.Length,
+                                       bindValue,
+                                       bindSize,
+                                       bindType,
+                                       ref indicator,
+                                       IntPtr.Zero,
+                                       IntPtr.Zero,
+                                       0,
+                                       IntPtr.Zero,
+                                       0);
+                       }
 
                        if (status != 0) {
                                OciErrorInfo info = connection.ErrorHandle.HandleError ();
@@ -244,10 +500,9 @@ namespace System.Data.OracleClient {
                        bindHandle.SetHandle (tmpHandle);
                }
 
-               [MonoTODO]
                object ICloneable.Clone ()
                {
-                       throw new NotImplementedException ();
+                       return new OracleParameter(this);
                }
 
                private void InferOracleType (object value)
@@ -265,7 +520,7 @@ namespace System.Data.OracleClient {
                        case "System.String":
                                SetOracleType (OracleType.VarChar);
                                break;
-                       case "System.DataType":
+                       case "System.DateTime":
                                SetOracleType (OracleType.DateTime);
                                break;
                        case "System.Decimal":
@@ -293,10 +548,53 @@ namespace System.Data.OracleClient {
                        }
                }
 
-               [MonoTODO ("different size depending on type.")]
                private int InferSize ()
                {
-                       return value.ToString ().Length;
+                       int newSize = 0;
+                       
+                       switch (ociType) {
+                       case OciDataType.VarChar2:
+                       case OciDataType.String:
+                       case OciDataType.VarChar:
+                       case OciDataType.Char:
+                       case OciDataType.CharZ:
+                       case OciDataType.OciString:
+                       case OciDataType.Long:
+                       case OciDataType.LongVarChar:
+                               if (value == null || value == DBNull.Value)
+                                       newSize = 0;
+                               else
+                                       newSize = value.ToString ().Length;
+                               break;
+                       case OciDataType.RowIdDescriptor:
+                               newSize = 10;
+                               break;
+                       case OciDataType.Integer:
+                       case OciDataType.Number:
+                       case OciDataType.Float:
+                               newSize = 22;
+                               break;
+                       case OciDataType.Date:
+                               newSize = 7;
+                               break;
+                       case OciDataType.TimeStamp:
+                               newSize = 11;
+                               break;          
+                       case OciDataType.Blob:
+                       case OciDataType.Clob:
+                               newSize = -1;
+                               break;                                  
+                       default:
+                               if (value == null || value == DBNull.Value)
+                                       newSize = 0;
+                               else
+                                       newSize = value.ToString ().Length;
+                               break;
+                       }
+
+                       sizeSet = true;
+
+                       return newSize;
                }
 
                private void SetDbType (DbType type)
@@ -374,6 +672,9 @@ namespace System.Data.OracleClient {
                        switch (type) {
                        case OracleType.BFile:
                        case OracleType.Blob:
+                               dbType = DbType.Binary;
+                               ociType = OciDataType.Blob;
+                               break;
                        case OracleType.LongRaw:
                        case OracleType.Raw:
                                dbType = DbType.Binary;
@@ -381,13 +682,16 @@ namespace System.Data.OracleClient {
                                break;
                        case OracleType.Byte:
                                dbType = DbType.Byte;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.Char:
-                               dbType = DbType.AnsiStringFixedLength;
+                               dbType = DbType.AnsiString;
                                ociType = OciDataType.Char;
                                break;
                        case OracleType.Clob:
+                               dbType = DbType.AnsiString;
+                               ociType = OciDataType.Clob;
+                               break;
                        case OracleType.LongVarChar:
                        case OracleType.RowId:
                        case OracleType.VarChar:
@@ -396,32 +700,35 @@ namespace System.Data.OracleClient {
                                break;
                        case OracleType.Cursor:
                        case OracleType.IntervalDayToSecond:
-                               dbType = DbType.Object;
-                               ociType = OciDataType.Blob;
+                               dbType = DbType.AnsiStringFixedLength;
+                               ociType = OciDataType.Char;
                                break;
-                       case OracleType.DateTime:
                        case OracleType.Timestamp:
                        case OracleType.TimestampLocal:
                        case OracleType.TimestampWithTZ:
                                dbType = DbType.DateTime;
-                               ociType = OciDataType.Char;
+                               ociType = OciDataType.TimeStamp;
+                               break;
+                       case OracleType.DateTime:
+                               dbType = DbType.DateTime;
+                               ociType = OciDataType.Date;
                                break;
                        case OracleType.Double:
                                dbType = DbType.Double;
-                               ociType = OciDataType.Float;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.Float:
                                dbType = DbType.Single;
-                               ociType = OciDataType.Float;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.Int16:
                                dbType = DbType.Int16;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.Int32:
                        case OracleType.IntervalYearToMonth:
                                dbType = DbType.Int32;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.NChar:
                                dbType = DbType.StringFixedLength;
@@ -430,7 +737,7 @@ namespace System.Data.OracleClient {
                        case OracleType.NClob:
                        case OracleType.NVarChar:
                                dbType = DbType.String;
-                               ociType = OciDataType.VarChar;
+                               ociType = OciDataType.Char;
                                break;
                        case OracleType.Number:
                                dbType = DbType.VarNumeric;
@@ -438,15 +745,15 @@ namespace System.Data.OracleClient {
                                break;
                        case OracleType.SByte:
                                dbType = DbType.SByte;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.UInt16:
                                dbType = DbType.UInt16;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        case OracleType.UInt32:
                                dbType = DbType.UInt32;
-                               ociType = OciDataType.Integer;
+                               ociType = OciDataType.Number;
                                break;
                        default:
                                throw new ArgumentException (exception);
@@ -460,6 +767,99 @@ namespace System.Data.OracleClient {
                        return ParameterName;
                }
 
+               internal void Update (OracleCommand cmd) 
+               {
+                       // used to update the parameter value
+                       // for Output, the output of InputOutput, and Return parameters
+                       value = DBNull.Value;
+                       if (indicator == -1)
+                               return;
+
+                       byte[] buffer = null;
+                       object tmp = null;
+
+                       switch (ociType) {
+                       case OciDataType.VarChar2:
+                       case OciDataType.String:
+                       case OciDataType.VarChar:
+                       case OciDataType.Char:
+                       case OciDataType.CharZ:
+                       case OciDataType.OciString:
+                       case OciDataType.RowIdDescriptor:
+                               buffer = new byte [Size];
+                               Marshal.Copy (bindOutValue, buffer, 0, Size);
+                               
+                               // Get length of returned string
+                               int     rsize = 0;
+                               IntPtr  env = cmd.Connection.Environment;
+                               OciCalls.OCICharSetToUnicode (env, null, buffer, out rsize);
+                       
+                               // Get string
+                               StringBuilder ret = new StringBuilder(rsize);
+                               OciCalls.OCICharSetToUnicode (env, ret, buffer, out rsize);
+       
+                               value = ret.ToString ();
+                               break;
+                       case OciDataType.Integer:
+                       case OciDataType.Number:
+                       case OciDataType.Float:
+                               tmp = Marshal.PtrToStringAnsi (bindOutValue, bindSize);
+                               if (tmp != null)
+                                       value = Decimal.Parse (String.Copy ((string) tmp));
+                               break;
+                       case OciDataType.TimeStamp:
+                               value = dateTimeDesc.GetDateTime (connection.Environment, dateTimeDesc.ErrorHandle);
+                               break;
+                       case OciDataType.Date:
+                               value = UnpackDate (bindOutValue);
+                               break;  
+                       case OciDataType.Blob:
+                       case OciDataType.Clob:
+                               OracleLob lob = new OracleLob (lobLocator, ociType);
+                               lob.connection = connection;
+                               value = lob;
+                               break;
+                       default:
+                               throw new NotImplementedException ();
+                       }
+               }
+
+               // copied from OciDefineHandle
+               [MonoTODO ("Be able to handle negative dates... i.e. BCE.")]
+               internal DateTime UnpackDate (IntPtr dateValue)
+               {
+                       byte century = Marshal.ReadByte (dateValue, 0);
+                       byte year = Marshal.ReadByte (dateValue, 1);
+                       byte month = Marshal.ReadByte (dateValue, 2);
+                       byte day = Marshal.ReadByte (dateValue, 3);
+                       byte hour = Marshal.ReadByte (dateValue, 4);
+                       byte minute = Marshal.ReadByte (dateValue, 5);
+                       byte second = Marshal.ReadByte (dateValue, 6);
+
+                       return new DateTime ((century - 100) * 100 + (year - 100),
+                                               month,
+                                               day,
+                                               hour - 1,
+                                               minute - 1,
+                                               second - 1);
+
+               }
+
+               internal byte[] PackDate (DateTime dateValue) 
+               {
+                       byte[] buffer = new byte[7];
+
+                       buffer[0] = (byte)((dateValue.Year / 100) + 100); //century
+                       buffer[1] = (byte)((dateValue.Year % 100) + 100); // Year
+                       buffer[2] = (byte)dateValue.Month;
+                       buffer[3] = (byte)dateValue.Day;
+                       buffer[4] = (byte)(dateValue.Hour+1);
+                       buffer[5] = (byte)(dateValue.Minute+1);
+                       buffer[6] = (byte)(dateValue.Second+1);
+
+                       return buffer;
+               }
+
                #endregion // Methods
 
                internal sealed class OracleParameterConverter : ExpandableObjectConverter