File: OleDbParameter.cs
Web Access
Project: src\src\runtime\src\libraries\System.Data.OleDb\src\System.Data.OleDb.csproj (System.Data.OleDb)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace System.Data.OleDb
{
    [TypeConverter(typeof(OleDbParameter.OleDbParameterConverter))]
    public sealed partial class OleDbParameter : DbParameter, ICloneable, IDbDataParameter
    {
        private NativeDBType? _metaType;
        private int _changeID;

        private string? _parameterName;
        private byte _precision;
        private byte _scale;
        private bool _hasScale;

        private NativeDBType? _coerceMetaType;

        public OleDbParameter() : base()
        { // V1.0 nothing
        }

        public OleDbParameter(string? name, object? value) : this()
        {
            Debug.Assert(!(value is OleDbType), "use OleDbParameter(string, OleDbType)");
            Debug.Assert(!(value is SqlDbType), "use OleDbParameter(string, OleDbType)");

            ParameterName = name;
            Value = value;
        }

        public OleDbParameter(string? name, OleDbType dataType) : this()
        {
            ParameterName = name;
            OleDbType = dataType;
        }

        public OleDbParameter(string? name, OleDbType dataType, int size) : this()
        {
            ParameterName = name;
            OleDbType = dataType;
            Size = size;
        }

        public OleDbParameter(string? name, OleDbType dataType, int size, string? srcColumn) : this()
        {
            ParameterName = name;
            OleDbType = dataType;
            Size = size;
            SourceColumn = srcColumn;
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public OleDbParameter(string? parameterName,
                              OleDbType dbType, int size,
                              ParameterDirection direction, bool isNullable,
                              byte precision, byte scale,
                              string? srcColumn, DataRowVersion srcVersion,
                              object? value) : this()
        { // V1.0 everything
            ParameterName = parameterName;
            OleDbType = dbType;
            Size = size;
            Direction = direction;
            IsNullable = isNullable;
            PrecisionInternal = precision;
            ScaleInternal = scale;
            SourceColumn = srcColumn;
            SourceVersion = srcVersion;
            Value = value;
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public OleDbParameter(string? parameterName,
                              OleDbType dbType, int size,
                              ParameterDirection direction,
                              byte precision, byte scale,
                              string? sourceColumn, DataRowVersion sourceVersion, bool sourceColumnNullMapping,
                              object? value) : this()
        { // V2.0 everything - round trip all browsable properties + precision/scale
            ParameterName = parameterName;
            OleDbType = dbType;
            Size = size;
            Direction = direction;
            PrecisionInternal = precision;
            ScaleInternal = scale;
            SourceColumn = sourceColumn;
            SourceVersion = sourceVersion;
            SourceColumnNullMapping = sourceColumnNullMapping;
            Value = value;
        }

        internal int ChangeID
        {
            get
            {
                return _changeID;
            }
        }

        public override DbType DbType
        {
            get
            {
                return GetBindType(Value).enumDbType;
            }
            set
            {
                NativeDBType? dbtype = _metaType;
                if ((null == dbtype) || (dbtype.enumDbType != value))
                {
                    PropertyTypeChanging();
                    _metaType = NativeDBType.FromDbType(value);
                }
            }
        }

        public override void ResetDbType()
        {
            ResetOleDbType();
        }

        [
        RefreshProperties(RefreshProperties.All),
        DbProviderSpecificTypeProperty(true),
        ]
        public OleDbType OleDbType
        {
            get
            {
                return GetBindType(Value).enumOleDbType;
            }
            set
            {
                NativeDBType? dbtype = _metaType;
                if ((null == dbtype) || (dbtype.enumOleDbType != value))
                {
                    PropertyTypeChanging();
                    _metaType = NativeDBType.FromDataType(value);
                }
            }
        }

        private bool ShouldSerializeOleDbType()
        {
            return (null != _metaType);
        }

        public void ResetOleDbType()
        {
            if (null != _metaType)
            {
                PropertyTypeChanging();
                _metaType = null;
            }
        }

        [AllowNull]
        public override string ParameterName
        { // V1.2.3300, XXXParameter V1.0.3300
            get
            {
                return _parameterName ?? string.Empty;
            }
            set
            {
                if (_parameterName != value)
                {
                    PropertyChanging();
                    _parameterName = value;
                }
            }
        }

        [DefaultValue((byte)0)]
        public new byte Precision
        {
            get
            {
                return PrecisionInternal;
            }
            set
            {
                PrecisionInternal = value;
            }
        }
        internal byte PrecisionInternal
        {
            get
            {
                byte precision = _precision;
                if (0 == precision)
                {
                    precision = ValuePrecision(Value);
                }
                return precision;
            }
            set
            {
                if (_precision != value)
                {
                    PropertyChanging();
                    _precision = value;
                }
            }
        }
        private bool ShouldSerializePrecision()
        {
            return (0 != _precision);
        }

        [DefaultValue((byte)0)]
        public new byte Scale
        {
            get
            {
                return ScaleInternal;
            }
            set
            {
                ScaleInternal = value;
            }
        }
        internal byte ScaleInternal
        {
            get
            {
                byte scale = _scale;
                if (!ShouldSerializeScale(scale))
                {
                    scale = ValueScale(Value);
                }
                return scale;
            }
            set
            {
                if (_scale != value || !_hasScale)
                {
                    PropertyChanging();
                    _scale = value;
                    _hasScale = true;
                }
            }
        }
        private bool ShouldSerializeScale()
        {
            return ShouldSerializeScale(_scale);
        }

        private bool ShouldSerializeScale(byte scale)
        {
            return _hasScale && ((0 != scale) || ShouldSerializePrecision());
        }

        object ICloneable.Clone()
        {
            return new OleDbParameter(this);
        }

        private void CloneHelper(OleDbParameter destination)
        {
            CloneHelperCore(destination);
            destination._metaType = _metaType;
            destination._parameterName = _parameterName;
            destination._precision = _precision;
            destination._scale = _scale;
            destination._hasScale = _hasScale;
        }

        private void PropertyChanging()
        {
            unchecked
            { _changeID++; }
        }

        private void PropertyTypeChanging()
        {
            PropertyChanging();
            _coerceMetaType = null;
            CoercedValue = null;
        }

        // goal: call virtual property getters only once per parameter
        internal bool BindParameter(int index, Bindings bindings)
        {
            int changeID = _changeID;
            object? value = Value;

            NativeDBType dbtype = GetBindType(value);
            if (OleDbType.Empty == dbtype.enumOleDbType)
            {
                throw ODB.UninitializedParameters(index, dbtype.enumOleDbType);
            }
            _coerceMetaType = dbtype;
            value = CoerceValue(value, dbtype);
            CoercedValue = value;

            ParameterDirection direction = Direction;

            byte precision;
            if (ShouldSerializePrecision())
            {
                precision = PrecisionInternal;
            }
            else
            {
                precision = ValuePrecision(value);
            }
            if (0 == precision)
            {
                precision = dbtype.maxpre;
            }

            byte scale;
            if (ShouldSerializeScale())
            {
                scale = ScaleInternal;
            }
            else
            {
                scale = ValueScale(value);
            }

            int wtype = dbtype.wType;
            int bytecount, size;

            if (dbtype.islong)
            { // long data (image, text, ntext)
                bytecount = IntPtr.Size;
                if (ShouldSerializeSize())
                {
                    size = Size;
                }
                else
                {
                    if (NativeDBType.STR == dbtype.dbType)
                    {
                        size = int.MaxValue;
                    }
                    else if (NativeDBType.WSTR == dbtype.dbType)
                    {
                        size = int.MaxValue / 2;
                    }
                    else
                    {
                        size = int.MaxValue;
                    }
                }
                wtype |= NativeDBType.BYREF;
            }
            else if (dbtype.IsVariableLength)
            { // variable length data (varbinary, varchar, nvarchar)
                if (!ShouldSerializeSize() && ADP.IsDirection(this, ParameterDirection.Output))
                {
                    throw ADP.UninitializedParameterSize(index, _coerceMetaType.dataType!);
                }

                bool computedSize;
                if (ShouldSerializeSize())
                {
                    size = Size;
                    computedSize = false;
                }
                else
                {
                    size = ValueSize(value);
                    computedSize = true;
                }
                if (0 < size)
                {
                    if (NativeDBType.WSTR == dbtype.wType)
                    {
                        // maximum 0x3FFFFFFE characters, computed this way to avoid overflow exception
                        bytecount = Math.Min(size, 0x3FFFFFFE) * 2 + 2;
                    }
                    else
                    {
                        Debug.Assert(NativeDBType.STR != dbtype.wType, "should have ANSI binding, describing is okay");
                        bytecount = size;
                    }

                    if (computedSize)
                    {
                        if (NativeDBType.STR == dbtype.dbType)
                        {
                            // maximum 0x7ffffffe characters, computed this way to avoid overflow exception
                            size = Math.Min(size, 0x3FFFFFFE) * 2;
                        }
                    }

                    if (ODB.LargeDataSize < bytecount)
                    {
                        bytecount = IntPtr.Size;
                        wtype |= NativeDBType.BYREF;
                    }
                }
                else if (0 == size)
                {
                    if (NativeDBType.WSTR == wtype)
                    { // allow space for null termination character
                        bytecount = 2;
                        // 0 == size, okay for (STR == dbType)
                    }
                    else
                    {
                        Debug.Assert(NativeDBType.STR != dbtype.wType, "should have ANSI binding, describing is okay");
                        bytecount = 0;
                    }
                }
                else if (-1 == size)
                {
                    bytecount = IntPtr.Size;
                    wtype |= NativeDBType.BYREF;
                }
                else
                {
                    throw ADP.InvalidSizeValue(size);
                }
            }
            else
            { // fixed length data
                bytecount = dbtype.fixlen;
                size = bytecount;
            }
            bindings.CurrentIndex = index;

            // tagDBPARAMBINDINFO info for SetParameterInfo
            bindings.DataSourceType = dbtype.dbString.DangerousGetHandle(); // NOTE: This is a constant and isn't exposed publicly, so there really isn't a potential for Handle Recycling.
            bindings.Name = IntPtr.Zero;
            bindings.ParamSize = new IntPtr(size);
            bindings.Flags = GetBindFlags(direction);
            //bindings.Precision    = precision;
            //bindings.Scale        = scale;

            // tagDBBINDING info for CreateAccessor
            bindings.Ordinal = (IntPtr)(index + 1);
            bindings.Part = dbtype.dbPart;
            bindings.ParamIO = GetBindDirection(direction);
            bindings.Precision = precision;
            bindings.Scale = scale;
            bindings.DbType = wtype;
            bindings.MaxLen = bytecount; // also increments databuffer size (uses DbType)
                                         //bindings.ValueOffset  = bindings.DataBufferSize; // set via MaxLen
                                         //bindings.LengthOffset = i * sizeof_int64;
                                         //bindings.StatusOffset = i * sizeof_int64 + sizeof_int32;
                                         //bindings.TypeInfoPtr  = 0;
                                         //bindings.ObjectPtr    = 0;
                                         //bindings.BindExtPtr   = 0;
                                         //bindings.MemOwner     = /*DBMEMOWNER_CLIENTOWNED*/0;
                                         //bindings.Flags        = 0;

            //bindings.ParameterChangeID = changeID; // bind until something changes
            Debug.Assert(_changeID == changeID, "parameter has unexpectedly changed");

            return IsParameterComputed();
        }

        private static object? CoerceValue(object? value, NativeDBType destinationType)
        {
            Debug.Assert(null != destinationType, "null destinationType");
            if ((null != value) && (DBNull.Value != value) && (typeof(object) != destinationType.dataType))
            {
                Type currentType = value.GetType();
                if (currentType != destinationType.dataType)
                {
                    try
                    {
                        if ((typeof(string) == destinationType.dataType) && (typeof(char[]) == currentType))
                        {
                        }
                        else if ((NativeDBType.CY == destinationType.dbType) && (typeof(string) == currentType))
                        {
                            value = decimal.Parse((string)value, NumberStyles.Currency, null);
                        }
                        else
                        {
                            value = Convert.ChangeType(value, destinationType.dataType!, null);
                        }
                    }
                    catch (Exception e)
                    {
                        // UNDONE - should not be catching all exceptions!!!
                        if (!ADP.IsCatchableExceptionType(e))
                        {
                            throw;
                        }

                        throw ADP.ParameterConversionFailed(value, destinationType.dataType!, e);
                    }
                }
            }
            return value;
        }

        private NativeDBType GetBindType(object? value)
        {
            NativeDBType? dbtype = _metaType;
            if (null == dbtype)
            {
                if (ADP.IsNull(value))
                {
                    dbtype = OleDb.NativeDBType.Default;
                }
                else
                {
                    dbtype = NativeDBType.FromSystemType(value);
                }
            }
            return dbtype;
        }

        internal object? GetCoercedValue()
        {
            object? value = CoercedValue; // will also be set during binding, will rebind everytime if _metaType not set
            if (null == value)
            {
                value = CoerceValue(Value, _coerceMetaType!);
                CoercedValue = value;
            }
            return value;
        }

        internal bool IsParameterComputed()
        {
            NativeDBType? metaType = _metaType;
            return ((null == metaType)
                    || (!ShouldSerializeSize() && metaType.IsVariableLength)
                    || ((NativeDBType.DECIMAL == metaType.dbType) || (NativeDBType.NUMERIC == metaType.dbType)
                        && (!ShouldSerializeScale() || !ShouldSerializePrecision())
                        )
                    );
        }

        // @devnote: use IsParameterComputed which is called in the normal case
        // only to call Prepare to throw the specialized error message
        // reducing the overall number of methods to actually jit
        internal void Prepare(OleDbCommand cmd)
        {
            Debug.Assert(IsParameterComputed(), "Prepare computed parameter");
            if (null == _metaType)
            {
                throw ADP.PrepareParameterType(cmd);
            }
            else if (!ShouldSerializeSize() && _metaType.IsVariableLength)
            {
                throw ADP.PrepareParameterSize(cmd);
            }
            else if (!ShouldSerializePrecision() && !ShouldSerializeScale() && ((NativeDBType.DECIMAL == _metaType.wType) || (NativeDBType.NUMERIC == _metaType.wType)))
            {
                throw ADP.PrepareParameterScale(cmd, _metaType.wType.ToString("G", CultureInfo.InvariantCulture));
            }
        }

        [
        RefreshProperties(RefreshProperties.All),
        TypeConverter(typeof(StringConverter)),
        ]
        public override object? Value
        { // V1.2.3300, XXXParameter V1.0.3300
            get
            {
                return _value;
            }
            set
            {
                _coercedValue = null;
                _value = value;
            }
        }

        private static byte ValuePrecision(object? value)
        {
            return ValuePrecisionCore(value);
        }

        private static byte ValueScale(object? value)
        {
            return ValueScaleCore(value);
        }

        private static int ValueSize(object? value)
        {
            return ValueSizeCore(value);
        }

        private static int GetBindDirection(ParameterDirection direction)
        {
            return (ODB.ParameterDirectionFlag & (int)direction);
            /*switch(Direction) {
            default:
            case ParameterDirection.Input:
                return ODB.DBPARAMIO_INPUT;
            case ParameterDirection.Output:
            case ParameterDirection.ReturnValue:
                return ODB.DBPARAMIO_OUTPUT;
            case ParameterDirection.InputOutput:
                return (ODB.DBPARAMIO_INPUT | ODB.DBPARAMIO_OUTPUT);
            }*/
        }

        private static int GetBindFlags(ParameterDirection direction)
        {
            return (ODB.ParameterDirectionFlag & (int)direction);
            /*switch(Direction) {
            default:
            case ParameterDirection.Input:
                return ODB.DBPARAMFLAGS_ISINPUT;
            case ParameterDirection.Output:
            case ParameterDirection.ReturnValue:
                return ODB.DBPARAMFLAGS_ISOUTPUT;
            case ParameterDirection.InputOutput:
                return (ODB.DBPARAMFLAGS_ISINPUT | ODB.DBPARAMFLAGS_ISOUTPUT);
            }*/
        }

        // implemented as nested class to take advantage of the private/protected ShouldSerializeXXX methods
        internal sealed class OleDbParameterConverter : System.ComponentModel.ExpandableObjectConverter
        {
            // converter classes should have public ctor
            public OleDbParameterConverter()
            {
            }

            public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
            {
                if (typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType)
                {
                    return true;
                }
                return base.CanConvertTo(context, destinationType);
            }

            public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
            {
                if (null == destinationType)
                {
                    throw ADP.ArgumentNull("destinationType");
                }
                if ((typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType) && (value is OleDbParameter parameter))
                {
                    return ConvertToInstanceDescriptor(parameter);
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }

            private static System.ComponentModel.Design.Serialization.InstanceDescriptor ConvertToInstanceDescriptor(OleDbParameter p)
            {
                int flags = 0;

                if (p.ShouldSerializeOleDbType())
                {
                    flags |= 1;
                }
                if (p.ShouldSerializeSize())
                {
                    flags |= 2;
                }
                if (!ADP.IsEmpty(p.SourceColumn))
                {
                    flags |= 4;
                }
                if (null != p.Value)
                {
                    flags |= 8;
                }
                if ((ParameterDirection.Input != p.Direction) || p.IsNullable
                    || p.ShouldSerializePrecision() || p.ShouldSerializeScale()
                    || (DataRowVersion.Current != p.SourceVersion))
                {
                    flags |= 16; // V1.0 everything
                }
                if (p.SourceColumnNullMapping)
                {
                    flags |= 32; // v2.0 everything
                }

                Type[] ctorParams;
                object?[] ctorValues;
                switch (flags)
                {
                    case 0: // ParameterName
                    case 1: // OleDbType
                        ctorParams = new Type[] { typeof(string), typeof(OleDbType) };
                        ctorValues = new object[] { p.ParameterName, p.OleDbType };
                        break;
                    case 2: // Size
                    case 3: // Size, OleDbType
                        ctorParams = new Type[] { typeof(string), typeof(OleDbType), typeof(int) };
                        ctorValues = new object[] { p.ParameterName, p.OleDbType, p.Size };
                        break;
                    case 4: // SourceColumn
                    case 5: // SourceColumn, OleDbType
                    case 6: // SourceColumn, Size
                    case 7: // SourceColumn, Size, OleDbType
                        ctorParams = new Type[] { typeof(string), typeof(OleDbType), typeof(int), typeof(string) };
                        ctorValues = new object[] { p.ParameterName, p.OleDbType, p.Size, p.SourceColumn };
                        break;
                    case 8: // Value
                        ctorParams = new Type[] { typeof(string), typeof(object) };
                        ctorValues = new object?[] { p.ParameterName, p.Value };
                        break;
                    default: // everything else
                        if (0 == (32 & flags))
                        { // V1.0 everything
                            ctorParams = new Type[] {
                            typeof(string), typeof(OleDbType), typeof(int), typeof(ParameterDirection),
                            typeof(bool), typeof(byte), typeof(byte), typeof(string),
                            typeof(DataRowVersion), typeof(object) };
                            ctorValues = new object?[] {
                            p.ParameterName, p.OleDbType,  p.Size, p.Direction,
                            p.IsNullable, p.PrecisionInternal, p.ScaleInternal, p.SourceColumn,
                            p.SourceVersion, p.Value };
                        }
                        else
                        { // v2.0 everything - round trip all browsable properties + precision/scale
                            ctorParams = new Type[] {
                            typeof(string), typeof(OleDbType), typeof(int), typeof(ParameterDirection),
                            typeof(byte), typeof(byte),
                            typeof(string), typeof(DataRowVersion), typeof(bool),
                            typeof(object) };
                            ctorValues = new object?[] {
                            p.ParameterName, p.OleDbType,  p.Size, p.Direction,
                            p.PrecisionInternal, p.ScaleInternal,
                            p.SourceColumn, p.SourceVersion, p.SourceColumnNullMapping,
                            p.Value };
                        }
                        break;
                }
                System.Reflection.ConstructorInfo ctor = typeof(OleDbParameter).GetConstructor(ctorParams)!;
                return new System.ComponentModel.Design.Serialization.InstanceDescriptor(ctor, ctorValues);
            }
        }
    }
}