File: ColumnBinding.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.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

namespace System.Data.OleDb
{
    internal sealed class ColumnBinding
    {
        // shared with other ColumnBindings
        private readonly OleDbDataReader _dataReader; // HCHAPTER
        private readonly RowBinding _rowbinding; // for native buffer interaction
        private readonly Bindings _bindings;

        // unique to this ColumnBinding
        private readonly OleDbParameter? _parameter; // output value
        private readonly int _parameterChangeID;
        private readonly int _offsetStatus;
        private readonly int _offsetLength;
        private readonly int _offsetValue;

        // Delegate ad hoc created 'Marshal.GetIDispatchForObject' reflection object cache
        private static Func<object, IntPtr>? s_getIDispatchForObject;

        private readonly int _ordinal;
        private readonly int _maxLen;
        private readonly short _wType;
        private readonly byte _precision;

        private readonly int _index;
        private readonly int _indexForAccessor;    // HCHAPTER
        private readonly int _indexWithinAccessor; // HCHAPTER

        private readonly bool _ifIRowsetElseIRow;

        // unique per current input value
        private int _valueBindingOffset;
        private int _valueBindingSize;
        internal StringMemHandle? _sptr;
        private GCHandle _pinnedBuffer;

        // value is cached via property getters so the original may be released
        // for Value, ValueByteArray, ValueString, ValueVariant
        private object? _value;

        internal ColumnBinding(OleDbDataReader dataReader, int index, int indexForAccessor, int indexWithinAccessor,
                                OleDbParameter? parameter, RowBinding rowbinding, Bindings bindings, tagDBBINDING binding, int offset,
                                bool ifIRowsetElseIRow)
        {
            Debug.Assert(null != rowbinding, "null rowbinding");
            Debug.Assert(null != bindings, "null bindings");
            Debug.Assert(ODB.SizeOf_tagDBBINDING <= offset, $"invalid offset {offset}");

            _dataReader = dataReader;
            _rowbinding = rowbinding;
            _bindings = bindings;
            _index = index;
            _indexForAccessor = indexForAccessor;
            _indexWithinAccessor = indexWithinAccessor;

            if (null != parameter)
            {
                _parameter = parameter;
                _parameterChangeID = parameter.ChangeID;
            }
            _offsetStatus = binding.obStatus.ToInt32() + offset;
            _offsetLength = binding.obLength.ToInt32() + offset;
            _offsetValue = binding.obValue.ToInt32() + offset;

            Debug.Assert(0 <= _offsetStatus, "negative _offsetStatus");
            Debug.Assert(0 <= _offsetLength, "negative _offsetLength");
            Debug.Assert(0 <= _offsetValue, "negative _offsetValue");

            _ordinal = binding.iOrdinal.ToInt32();
            _maxLen = binding.cbMaxLen.ToInt32();
            _wType = binding.wType;
            _precision = binding.bPrecision;

            _ifIRowsetElseIRow = ifIRowsetElseIRow;

            SetSize(Bindings.ParamSize.ToInt32());
        }

        internal Bindings Bindings
        {
            get
            {
                _bindings.CurrentIndex = IndexWithinAccessor;
                return _bindings;
            }
        }

        internal RowBinding RowBinding
        {
            get { return _rowbinding; }
        }

        internal int ColumnBindingOrdinal
        {
            get { return _ordinal; }
        }

        private int ColumnBindingMaxLen
        {
            get { return _maxLen; }
        }

        private byte ColumnBindingPrecision
        {
            get { return _precision; }
        }

        private short DbType
        {
            get { return _wType; }
        }

        private Type ExpectedType
        {
            get { return NativeDBType.FromDBType(DbType, false, false).dataType!; }
        }

        internal int Index
        {
            get { return _index; }
        }
        internal int IndexForAccessor
        {
            get { return _indexForAccessor; }
        }

        internal int IndexWithinAccessor
        {
            get { return _indexWithinAccessor; }
        }

        private int ValueBindingOffset
        { // offset within the value of where to start copying
            get { return _valueBindingOffset; }
        }

        private int ValueBindingSize
        { // maximum size of the value to copy
            get { return _valueBindingSize; }
        }

        internal int ValueOffset
        { // offset within the native buffer to put the value
            get { return _offsetValue; }
        }

        private OleDbDataReader DataReader()
        {
            Debug.Assert(null != _dataReader, "null DataReader");
            return _dataReader;
        }

        internal bool IsParameterBindingInvalid(OleDbParameter parameter)
        {
            Debug.Assert((null != _parameter) && (null != parameter), "null parameter");
            return ((_parameter.ChangeID != _parameterChangeID) || (_parameter != parameter));
        }

        internal bool IsValueNull()
        {
            return ((DBStatus.S_ISNULL == StatusValue())
                    || (((NativeDBType.VARIANT == DbType) || (NativeDBType.PROPVARIANT == DbType))
                        && (Convert.IsDBNull(ValueVariant()))));
        }

        private int LengthValue()
        {
            int length;
            if (_ifIRowsetElseIRow)
            {
                length = RowBinding.ReadIntPtr(_offsetLength).ToInt32();
            }
            else
            {
                length = Bindings.DBColumnAccess[IndexWithinAccessor].cbDataLen.ToInt32();
            }
            return Math.Max(length, 0);
        }
        private void LengthValue(int value)
        {
            Debug.Assert(0 <= value, "negative LengthValue");
            RowBinding.WriteIntPtr(_offsetLength, (IntPtr)value);
        }

        internal OleDbParameter Parameter()
        {
            Debug.Assert(null != _parameter, "null parameter");
            return _parameter;
        }

        internal void ResetValue()
        {
            _value = null;

            StringMemHandle? sptr = _sptr;
            _sptr = null;

            sptr?.Dispose();

            if (_pinnedBuffer.IsAllocated)
            {
                _pinnedBuffer.Free();
            }
        }

        internal DBStatus StatusValue()
        {
            if (_ifIRowsetElseIRow)
            {
                return (DBStatus)RowBinding.ReadInt32(_offsetStatus);
            }
            else
            {
                return (DBStatus)Bindings.DBColumnAccess[IndexWithinAccessor].dwStatus;
            }
        }
        internal void StatusValue(DBStatus value)
        {
#if DEBUG
            switch (value)
            {
                case DBStatus.S_OK:
                case DBStatus.S_ISNULL:
                case DBStatus.S_DEFAULT:
                    break;
                default:
                    Debug.Fail("unexpected StatusValue");
                    break;
            }
#endif
            RowBinding.WriteInt32(_offsetStatus, (int)value);
        }

        internal void SetOffset(int offset)
        {
            if (0 > offset)
            {
                throw ADP.InvalidOffsetValue(offset);
            }
            _valueBindingOffset = Math.Max(offset, 0);
        }

        internal void SetSize(int size)
        {
            _valueBindingSize = Math.Max(size, 0);
        }

        private void SetValueDBNull()
        {
            LengthValue(0);
            StatusValue(DBStatus.S_ISNULL);
            RowBinding.WriteInt64(ValueOffset, 0); // safe because AlignDataSize forces 8 byte blocks
        }
        private void SetValueEmpty()
        {
            LengthValue(0);
            StatusValue(DBStatus.S_DEFAULT);
            RowBinding.WriteInt64(ValueOffset, 0); // safe because AlignDataSize forces 8 byte blocks
        }

        internal object Value()
        {
            object value;
            if (null == _value)
            {
                switch (StatusValue())
                {
                    case DBStatus.S_OK:
                        switch (DbType)
                        {
                            case NativeDBType.EMPTY:
                            case NativeDBType.NULL:
                                value = DBNull.Value;
                                break;
                            case NativeDBType.I2:
                                value = Value_I2(); // Int16
                                break;
                            case NativeDBType.I4:
                                value = Value_I4(); // Int32
                                break;
                            case NativeDBType.R4:
                                value = Value_R4(); // Single
                                break;
                            case NativeDBType.R8:
                                value = Value_R8(); // Double
                                break;
                            case NativeDBType.CY:
                                value = Value_CY(); // Decimal
                                break;
                            case NativeDBType.DATE:
                                value = Value_DATE(); // DateTime
                                break;
                            case NativeDBType.BSTR:
                                value = Value_BSTR(); // String
                                break;
                            case NativeDBType.IDISPATCH:
                                value = Value_IDISPATCH(); // Object
                                break;
                            case NativeDBType.ERROR:
                                value = Value_ERROR(); // Int32
                                break;
                            case NativeDBType.BOOL:
                                value = Value_BOOL(); // Boolean
                                break;
                            case NativeDBType.VARIANT:
                                value = Value_VARIANT(); // Object
                                break;
                            case NativeDBType.IUNKNOWN:
                                value = Value_IUNKNOWN(); // Object
                                break;
                            case NativeDBType.DECIMAL:
                                value = Value_DECIMAL(); // Decimal
                                break;
                            case NativeDBType.I1:
                                value = (short)Value_I1(); // SByte->Int16
                                break;
                            case NativeDBType.UI1:
                                value = Value_UI1(); // Byte
                                break;
                            case NativeDBType.UI2:
                                value = (int)Value_UI2(); // UInt16->Int32
                                break;
                            case NativeDBType.UI4:
                                value = (long)Value_UI4(); // UInt32->Int64
                                break;
                            case NativeDBType.I8:
                                value = Value_I8(); // Int64
                                break;
                            case NativeDBType.UI8:
                                value = (decimal)Value_UI8(); // UInt64->Decimal
                                break;
                            case NativeDBType.FILETIME:
                                value = Value_FILETIME(); // DateTime
                                break;
                            case NativeDBType.GUID:
                                value = Value_GUID(); // Guid
                                break;
                            case NativeDBType.BYTES:
                                value = Value_BYTES(); // Byte[]
                                break;
                            case NativeDBType.WSTR:
                                value = Value_WSTR(); // String
                                break;
                            case NativeDBType.NUMERIC:
                                value = Value_NUMERIC(); // Decimal
                                break;
                            case NativeDBType.DBDATE:
                                value = Value_DBDATE(); // DateTime
                                break;
                            case NativeDBType.DBTIME:
                                value = Value_DBTIME(); // TimeSpan
                                break;
                            case NativeDBType.DBTIMESTAMP:
                                value = Value_DBTIMESTAMP(); // DateTime
                                break;
                            case NativeDBType.PROPVARIANT:
                                value = Value_VARIANT(); // Object
                                break;
                            case NativeDBType.HCHAPTER:
                                value = Value_HCHAPTER(); // OleDbDataReader
                                break;
                            case (NativeDBType.BYREF | NativeDBType.BYTES):
                                value = Value_ByRefBYTES();
                                break;
                            case (NativeDBType.BYREF | NativeDBType.WSTR):
                                value = Value_ByRefWSTR();
                                break;
                            default:
                                throw ODB.GVtUnknown(DbType);
#if DEBUG
                            case NativeDBType.STR:
                                Debug.Fail("should have bound as WSTR");
                                goto default;
                            case NativeDBType.VARNUMERIC:
                                Debug.Fail("should have bound as NUMERIC");
                                goto default;
                            case NativeDBType.UDT:
                                Debug.Fail("UDT binding should not have been encountered");
                                goto default;
                            case (NativeDBType.BYREF | NativeDBType.STR):
                                Debug.Fail("should have bound as BYREF|WSTR");
                                goto default;
#endif
                        }
                        break;
                    case DBStatus.S_TRUNCATED:
                        switch (DbType)
                        {
                            case NativeDBType.BYTES:
                                value = Value_BYTES();
                                break;
                            case NativeDBType.WSTR:
                                value = Value_WSTR();
                                break;
                            case (NativeDBType.BYREF | NativeDBType.BYTES):
                                value = Value_ByRefBYTES();
                                break;
                            case (NativeDBType.BYREF | NativeDBType.WSTR):
                                value = Value_ByRefWSTR();
                                break;
                            default:
                                throw ODB.GVtUnknown(DbType);
#if DEBUG
                            case NativeDBType.STR:
                                Debug.Fail("should have bound as WSTR");
                                goto default;
                            case (NativeDBType.BYREF | NativeDBType.STR):
                                Debug.Fail("should have bound as BYREF|WSTR");
                                goto default;
#endif
                        }
                        break;
                    case DBStatus.S_ISNULL:
                    case DBStatus.S_DEFAULT:
                        value = DBNull.Value;
                        break;
                    default:
                        throw CheckTypeValueStatusValue();
                }
                _value = value;
            }
            return _value!;
        }
        internal void Value(object? value)
        {
            if (null == value)
            {
                SetValueEmpty();
            }
            else if (Convert.IsDBNull(value))
            {
                SetValueDBNull();
            }
            else
                switch (DbType)
                {
                    case NativeDBType.EMPTY:
                        SetValueEmpty();
                        break;
                    case NativeDBType.NULL: // language null - no representation, use DBNull
                        SetValueDBNull();
                        break;
                    case NativeDBType.I2:
                        Value_I2((short)value);
                        break;
                    case NativeDBType.I4:
                        Value_I4((int)value);
                        break;
                    case NativeDBType.R4:
                        Value_R4((float)value);
                        break;
                    case NativeDBType.R8:
                        Value_R8((double)value);
                        break;
                    case NativeDBType.CY:
                        Value_CY((decimal)value);
                        break;
                    case NativeDBType.DATE:
                        Value_DATE((DateTime)value);
                        break;
                    case NativeDBType.BSTR:
                        Value_BSTR((string)value);
                        break;
                    case NativeDBType.IDISPATCH:
                        Value_IDISPATCH(value);
                        break;
                    case NativeDBType.ERROR:
                        Value_ERROR((int)value);
                        break;
                    case NativeDBType.BOOL:
                        Value_BOOL((bool)value);
                        break;
                    case NativeDBType.VARIANT:
                        Value_VARIANT(value);
                        break;
                    case NativeDBType.IUNKNOWN:
                        Value_IUNKNOWN(value);
                        break;
                    case NativeDBType.DECIMAL:
                        Value_DECIMAL((decimal)value);
                        break;
                    case NativeDBType.I1:
                        if (value is short)
                        {
                            Value_I1(Convert.ToSByte((short)value, CultureInfo.InvariantCulture));
                        }
                        else
                        {
                            Value_I1((sbyte)value);
                        }
                        break;
                    case NativeDBType.UI1:
                        Value_UI1((byte)value);
                        break;
                    case NativeDBType.UI2:
                        if (value is int)
                        {
                            Value_UI2(Convert.ToUInt16((int)value, CultureInfo.InvariantCulture));
                        }
                        else
                        {
                            Value_UI2((ushort)value);
                        }
                        break;
                    case NativeDBType.UI4:
                        if (value is long)
                        {
                            Value_UI4(Convert.ToUInt32((long)value, CultureInfo.InvariantCulture));
                        }
                        else
                        {
                            Value_UI4((uint)value);
                        }
                        break;
                    case NativeDBType.I8:
                        Value_I8((long)value);
                        break;
                    case NativeDBType.UI8:
                        if (value is decimal)
                        {
                            Value_UI8(Convert.ToUInt64((decimal)value, CultureInfo.InvariantCulture));
                        }
                        else
                        {
                            Value_UI8((ulong)value);
                        }
                        break;
                    case NativeDBType.FILETIME:
                        Value_FILETIME((DateTime)value);
                        break;
                    case NativeDBType.GUID:
                        Value_GUID((Guid)value);
                        break;
                    case NativeDBType.BYTES:
                        Value_BYTES((byte[])value);
                        break;
                    case NativeDBType.WSTR:
                        if (value is string)
                        {
                            Value_WSTR((string)value);
                        }
                        else
                        {
                            Value_WSTR((char[])value);
                        }
                        break;
                    case NativeDBType.NUMERIC:
                        Value_NUMERIC((decimal)value);
                        break;
                    case NativeDBType.DBDATE:
                        Value_DBDATE((DateTime)value);
                        break;
                    case NativeDBType.DBTIME:
                        Value_DBTIME((TimeSpan)value);
                        break;
                    case NativeDBType.DBTIMESTAMP:
                        Value_DBTIMESTAMP((DateTime)value);
                        break;
                    case NativeDBType.PROPVARIANT:
                        Value_VARIANT(value);
                        break;
                    case (NativeDBType.BYREF | NativeDBType.BYTES):
                        Value_ByRefBYTES((byte[])value);
                        break;
                    case (NativeDBType.BYREF | NativeDBType.WSTR):
                        if (value is string)
                        {
                            Value_ByRefWSTR((string)value);
                        }
                        else
                        {
                            Value_ByRefWSTR((char[])value);
                        }
                        break;
                    default:
                        Debug.Fail("unknown DBTYPE");
                        throw ODB.SVtUnknown(DbType);
#if DEBUG
                    case NativeDBType.STR:
                        Debug.Fail("Should have bound as WSTR");
                        goto default;
                    case NativeDBType.UDT:
                        Debug.Fail("UDT binding should not have been encountered");
                        goto default;
                    case NativeDBType.HCHAPTER:
                        Debug.Fail("not allowed to set HCHAPTER");
                        goto default;
                    case NativeDBType.VARNUMERIC:
                        Debug.Fail("should have bound as NUMERIC");
                        goto default;
                    case (NativeDBType.BYREF | NativeDBType.STR):
                        Debug.Fail("should have bound as BYREF|WSTR");
                        goto default;
#endif
                }
        }

        internal bool Value_BOOL()
        {
            Debug.Assert((NativeDBType.BOOL == DbType), "Value_BOOL");
            Debug.Assert((DBStatus.S_OK == StatusValue()), "Value_BOOL");
            short value = RowBinding.ReadInt16(ValueOffset);
            return (0 != value);
        }
        private void Value_BOOL(bool value)
        {
            Debug.Assert((NativeDBType.BOOL == DbType), "Value_BOOL");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt16(ValueOffset, (short)(value ? ODB.VARIANT_TRUE : ODB.VARIANT_FALSE));
        }

        private string Value_BSTR()
        {
            Debug.Assert((NativeDBType.BSTR == DbType), "Value_BSTR");
            Debug.Assert((DBStatus.S_OK == StatusValue()), "Value_BSTR");
            string value = "";
            RowBinding bindings = RowBinding;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                bindings.DangerousAddRef(ref mustRelease);
                IntPtr ptr = bindings.ReadIntPtr(ValueOffset);
                if (IntPtr.Zero != ptr)
                {
                    value = Marshal.PtrToStringBSTR(ptr);
                }
            }
            finally
            {
                if (mustRelease)
                {
                    bindings.DangerousRelease();
                }
            }
            return value;
        }
        private void Value_BSTR(string value)
        {
            Debug.Assert((null != value), "Value_BSTR null");
            Debug.Assert((NativeDBType.BSTR == DbType), "Value_BSTR");
            LengthValue(value.Length * 2); /* bytecount*/
            StatusValue(DBStatus.S_OK);
            RowBinding.SetBstrValue(ValueOffset, value);
        }

        private byte[] Value_ByRefBYTES()
        {
            Debug.Assert(((NativeDBType.BYREF | NativeDBType.BYTES) == DbType), "Value_ByRefBYTES");
            Debug.Assert((DBStatus.S_OK == StatusValue()), "Value_ByRefBYTES");
            byte[]? value = null;
            RowBinding bindings = RowBinding;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                bindings.DangerousAddRef(ref mustRelease);
                IntPtr ptr = bindings.ReadIntPtr(ValueOffset);
                if (IntPtr.Zero != ptr)
                {
                    value = new byte[LengthValue()];
                    Marshal.Copy(ptr, value, 0, value.Length);
                }
            }
            finally
            {
                if (mustRelease)
                {
                    bindings.DangerousRelease();
                }
            }
            return value ?? Array.Empty<byte>();
        }
        private void Value_ByRefBYTES(byte[] value)
        {
            Debug.Assert(null != value, "Value_ByRefBYTES null");
            Debug.Assert((NativeDBType.BYREF | NativeDBType.BYTES) == DbType, "Value_ByRefBYTES");

            // we expect the provider/server to apply the silent truncation when binding BY_REF
            // if (value.Length < ValueBindingOffset) { throw "Offset must refer to a location within the value" }
            int length = ((ValueBindingOffset < value.Length) ? (value.Length - ValueBindingOffset) : 0);
            LengthValue(((0 < ValueBindingSize) ? Math.Min(ValueBindingSize, length) : length));
            StatusValue(DBStatus.S_OK);

            IntPtr ptr = IntPtr.Zero;
            if (0 < length)
            { // avoid pinning empty byte[]
                _pinnedBuffer = GCHandle.Alloc(value, GCHandleType.Pinned);
                ptr = _pinnedBuffer.AddrOfPinnedObject();
                ptr = ADP.IntPtrOffset(ptr, ValueBindingOffset);
            }
            RowBinding.SetByRefValue(ValueOffset, ptr);
        }

        private string Value_ByRefWSTR()
        {
            Debug.Assert((NativeDBType.BYREF | NativeDBType.WSTR) == DbType, "Value_ByRefWSTR");
            Debug.Assert((DBStatus.S_OK == StatusValue()) || (DBStatus.S_TRUNCATED == StatusValue()), "Value_ByRefWSTR");
            string value = "";
            RowBinding bindings = RowBinding;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                bindings.DangerousAddRef(ref mustRelease);
                IntPtr ptr = bindings.ReadIntPtr(ValueOffset);
                if (IntPtr.Zero != ptr)
                {
                    int charCount = LengthValue() / 2;
                    value = Marshal.PtrToStringUni(ptr, charCount);
                }
            }
            finally
            {
                if (mustRelease)
                {
                    bindings.DangerousRelease();
                }
            }
            return value;
        }
        private void Value_ByRefWSTR(string value)
        {
            Debug.Assert(null != value, "Value_ByRefWSTR null");
            Debug.Assert((NativeDBType.BYREF | NativeDBType.WSTR) == DbType, "Value_ByRefWSTR");
            // we expect the provider/server to apply the silent truncation when binding BY_REF
            // if (value.Length < ValueBindingOffset) { throw "Offset must refer to a location within the value" }
            int length = ((ValueBindingOffset < value.Length) ? (value.Length - ValueBindingOffset) : 0);
            LengthValue(((0 < ValueBindingSize) ? Math.Min(ValueBindingSize, length) : length) * 2); /* charcount->bytecount*/
            StatusValue(DBStatus.S_OK);

            IntPtr ptr = IntPtr.Zero;
            if (0 < length)
            { // avoid pinning empty string, i.e String.Empty
                _pinnedBuffer = GCHandle.Alloc(value, GCHandleType.Pinned);
                ptr = _pinnedBuffer.AddrOfPinnedObject();
                ptr = ADP.IntPtrOffset(ptr, ValueBindingOffset);
            }
            RowBinding.SetByRefValue(ValueOffset, ptr);
        }
        private void Value_ByRefWSTR(char[] value)
        {
            Debug.Assert(null != value, "Value_ByRefWSTR null");
            Debug.Assert((NativeDBType.BYREF | NativeDBType.WSTR) == DbType, "Value_ByRefWSTR");
            // we expect the provider/server to apply the silent truncation when binding BY_REF
            // if (value.Length < ValueBindingOffset) { throw "Offset must refer to a location within the value" }
            int length = ((ValueBindingOffset < value.Length) ? (value.Length - ValueBindingOffset) : 0);
            LengthValue(((0 < ValueBindingSize) ? Math.Min(ValueBindingSize, length) : length) * 2); /* charcount->bytecount*/
            StatusValue(DBStatus.S_OK);

            IntPtr ptr = IntPtr.Zero;
            if (0 < length)
            { // avoid pinning empty char[]
                _pinnedBuffer = GCHandle.Alloc(value, GCHandleType.Pinned);
                ptr = _pinnedBuffer.AddrOfPinnedObject();
                ptr = ADP.IntPtrOffset(ptr, ValueBindingOffset);
            }
            RowBinding.SetByRefValue(ValueOffset, ptr);
        }

        private byte[] Value_BYTES()
        {
            Debug.Assert(NativeDBType.BYTES == DbType, "Value_BYTES");
            Debug.Assert((DBStatus.S_OK == StatusValue()) || (DBStatus.S_TRUNCATED == StatusValue()), "Value_BYTES");
            int byteCount = Math.Min(LengthValue(), ColumnBindingMaxLen);
            byte[] value = new byte[byteCount];
            RowBinding.ReadBytes(ValueOffset, value, 0, byteCount);
            return value;
        }
        private void Value_BYTES(byte[] value)
        {
            Debug.Assert(null != value, "Value_BYTES null");
            // we silently truncate when the user has specified a given Size
            int bytecount = ((ValueBindingOffset < value.Length) ? Math.Min(value.Length - ValueBindingOffset, ColumnBindingMaxLen) : 0);
            LengthValue(bytecount);
            StatusValue(DBStatus.S_OK);
            if (0 < bytecount)
            {
                RowBinding.WriteBytes(ValueOffset, value, ValueBindingOffset, bytecount);
            }
        }

        private decimal Value_CY()
        {
            Debug.Assert(NativeDBType.CY == DbType, "Value_CY");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_CY");
            return decimal.FromOACurrency(RowBinding.ReadInt64(ValueOffset));
        }
        private void Value_CY(decimal value)
        {
            Debug.Assert(NativeDBType.CY == DbType, "Value_CY");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt64(ValueOffset, decimal.ToOACurrency(value));
        }

        private DateTime Value_DATE()
        {
            Debug.Assert(NativeDBType.DATE == DbType, "Value_DATE");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_DATE");
            return DateTime.FromOADate(RowBinding.ReadDouble(ValueOffset));
        }
        private void Value_DATE(DateTime value)
        {
            Debug.Assert(NativeDBType.DATE == DbType, "Value_DATE");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteDouble(ValueOffset, value.ToOADate());
        }

        private DateTime Value_DBDATE()
        {
            Debug.Assert(NativeDBType.DBDATE == DbType, "Value_DBDATE");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_DBDATE");
            return RowBinding.ReadDate(ValueOffset);
        }
        private void Value_DBDATE(DateTime value)
        {
            Debug.Assert(NativeDBType.DBDATE == DbType, "Value_DATE");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteDate(ValueOffset, value);
        }

        private TimeSpan Value_DBTIME()
        {
            Debug.Assert(NativeDBType.DBTIME == DbType, "Value_DBTIME");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_DBTIME");
            return RowBinding.ReadTime(ValueOffset);
        }
        private void Value_DBTIME(TimeSpan value)
        {
            Debug.Assert(NativeDBType.DBTIME == DbType, "Value_DBTIME");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteTime(ValueOffset, value);
        }

        private DateTime Value_DBTIMESTAMP()
        {
            Debug.Assert(NativeDBType.DBTIMESTAMP == DbType, "Value_DBTIMESTAMP");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_DBTIMESTAMP");
            return RowBinding.ReadDateTime(ValueOffset);
        }
        private void Value_DBTIMESTAMP(DateTime value)
        {
            Debug.Assert(NativeDBType.DBTIMESTAMP == DbType, "Value_DBTIMESTAMP");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteDateTime(ValueOffset, value);
        }

        private decimal Value_DECIMAL()
        {
            Debug.Assert(NativeDBType.DECIMAL == DbType, "Value_DECIMAL");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_DECIMAL");
            int[] buffer = new int[4];
            RowBinding.ReadInt32Array(ValueOffset, buffer, 0, 4);
            return new decimal(
                buffer[2],  // low
                buffer[3],  // mid
                buffer[1],  // high
                (0 != (buffer[0] & unchecked((int)0x80000000))), // sign
                unchecked((byte)((buffer[0] & unchecked((int)0x00FF0000)) >> 16))); // scale
        }
        private void Value_DECIMAL(decimal value)
        {
            Debug.Assert(NativeDBType.DECIMAL == DbType, "Value_DECIMAL");

            /* pending breaking change approval
            if (_precision < ((System.Data.SqlTypes.SqlDecimal) value).Precision) {
                throw ADP.ParameterValueOutOfRange(value);
            }
            */

            LengthValue(0);
            StatusValue(DBStatus.S_OK);

            int[] tmp = decimal.GetBits(value);
            int[] buffer = new int[4] {
                tmp[3], tmp[2], tmp[0], tmp[1]
            };
            RowBinding.WriteInt32Array(ValueOffset, buffer, 0, 4);
        }

        private int Value_ERROR()
        {
            Debug.Assert(NativeDBType.ERROR == DbType, "Value_ERROR");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_ERROR");
            return RowBinding.ReadInt32(ValueOffset);
        }
        private void Value_ERROR(int value)
        {
            Debug.Assert(NativeDBType.ERROR == DbType, "Value_ERROR");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt32(ValueOffset, value);
        }

        private DateTime Value_FILETIME()
        {
            Debug.Assert(NativeDBType.FILETIME == DbType, "Value_FILETIME");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_FILETIME");
            long tmp = RowBinding.ReadInt64(ValueOffset);
            return DateTime.FromFileTime(tmp);
        }
        private void Value_FILETIME(DateTime value)
        {
            Debug.Assert(NativeDBType.FILETIME == DbType, "Value_FILETIME");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            long tmp = value.ToFileTime();
            RowBinding.WriteInt64(ValueOffset, tmp);
        }

        internal Guid Value_GUID()
        {
            Debug.Assert(NativeDBType.GUID == DbType, "Value_GUID");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_GUID");
            return RowBinding.ReadGuid(ValueOffset);
        }
        private void Value_GUID(Guid value)
        {
            Debug.Assert(NativeDBType.GUID == DbType, "Value_GUID");

            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteGuid(ValueOffset, value);
        }

        internal OleDbDataReader Value_HCHAPTER()
        {
            Debug.Assert(NativeDBType.HCHAPTER == DbType, "Value_HCHAPTER");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_HCHAPTER");

            return DataReader().ResetChapter(IndexForAccessor, IndexWithinAccessor, RowBinding, ValueOffset);
        }

        private sbyte Value_I1()
        {
            Debug.Assert(NativeDBType.I1 == DbType, "Value_I1");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_I1");
            byte value = RowBinding.ReadByte(ValueOffset);
            return unchecked((sbyte)value);
        }
        private void Value_I1(sbyte value)
        {
            Debug.Assert(NativeDBType.I1 == DbType, "Value_I1");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteByte(ValueOffset, unchecked((byte)value));
        }

        internal short Value_I2()
        {
            Debug.Assert(NativeDBType.I2 == DbType, "Value_I2");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_I2");
            return RowBinding.ReadInt16(ValueOffset);
        }
        private void Value_I2(short value)
        {
            Debug.Assert(NativeDBType.I2 == DbType, "Value_I2");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt16(ValueOffset, value);
        }

        private int Value_I4()
        {
            Debug.Assert(NativeDBType.I4 == DbType, "Value_I4");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_I4");
            return RowBinding.ReadInt32(ValueOffset);
        }
        private void Value_I4(int value)
        {
            Debug.Assert(NativeDBType.I4 == DbType, "Value_I4");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt32(ValueOffset, value);
        }

        private long Value_I8()
        {
            Debug.Assert(NativeDBType.I8 == DbType, "Value_I8");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_I8");
            return RowBinding.ReadInt64(ValueOffset);
        }
        private void Value_I8(long value)
        {
            Debug.Assert(NativeDBType.I8 == DbType, "Value_I8");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt64(ValueOffset, value);
        }

        private object Value_IDISPATCH()
        {
            Debug.Assert(NativeDBType.IDISPATCH == DbType, "Value_IDISPATCH");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_IDISPATCH");
            object value;
            RowBinding bindings = RowBinding;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                bindings.DangerousAddRef(ref mustRelease);
                IntPtr ptr = bindings.ReadIntPtr(ValueOffset);
                value = Marshal.GetObjectForIUnknown(ptr);
            }
            finally
            {
                if (mustRelease)
                {
                    bindings.DangerousRelease();
                }
            }
            return value;
        }
        private void Value_IDISPATCH(object value)
        {
            // UNDONE: OLE DB will IUnknown.Release input storage parameter values
            Debug.Assert(NativeDBType.IDISPATCH == DbType, "Value_IDISPATCH");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);

            IntPtr ptr = IntPtr.Zero;
            // lazy init reflection objects
            if (s_getIDispatchForObject == null)
            {
                object? delegateInstance = null;
                MethodInfo? mi = typeof(Marshal).GetMethod("GetIDispatchForObject", BindingFlags.Public | BindingFlags.Static);
                if (mi == null)
                {
                    throw new NotSupportedException(SR.PlatformNotSupported_GetIDispatchForObject);
                }
                Volatile.Write(ref delegateInstance, mi.CreateDelegate(typeof(Func<object, IntPtr>)));
                s_getIDispatchForObject = delegateInstance as Func<object, IntPtr>;
                ptr = s_getIDispatchForObject!(value);
            }
            RowBinding.WriteIntPtr(ValueOffset, ptr);
        }

        private object Value_IUNKNOWN()
        {
            Debug.Assert(NativeDBType.IUNKNOWN == DbType, "Value_IUNKNOWN");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_IUNKNOWN");
            object value;
            RowBinding bindings = RowBinding;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                bindings.DangerousAddRef(ref mustRelease);
                IntPtr ptr = bindings.ReadIntPtr(ValueOffset);
                value = Marshal.GetObjectForIUnknown(ptr);
            }
            finally
            {
                if (mustRelease)
                {
                    bindings.DangerousRelease();
                }
            }
            return value;
        }
        private void Value_IUNKNOWN(object value)
        {
            // UNDONE: OLE DB will IUnknown.Release input storage parameter values
            Debug.Assert(NativeDBType.IUNKNOWN == DbType, "Value_IUNKNOWN");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            IntPtr ptr = Marshal.GetIUnknownForObject(value);
            RowBinding.WriteIntPtr(ValueOffset, ptr);
        }

        private decimal Value_NUMERIC()
        {
            Debug.Assert(NativeDBType.NUMERIC == DbType, "Value_NUMERIC");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_NUMERIC");
            return RowBinding.ReadNumeric(ValueOffset);
        }
        private void Value_NUMERIC(decimal value)
        {
            Debug.Assert(NativeDBType.NUMERIC == DbType, "Value_NUMERIC");

            /* pending breaking change approval
            if (_precision < ((System.Data.SqlTypes.SqlDecimal) value).Precision) {
                throw ADP.ParameterValueOutOfRange(value);
            }
            */

            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteNumeric(ValueOffset, value, ColumnBindingPrecision);
        }

        private float Value_R4()
        {
            Debug.Assert(NativeDBType.R4 == DbType, "Value_R4");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_R4");

            return RowBinding.ReadSingle(ValueOffset);
        }
        private void Value_R4(float value)
        {
            Debug.Assert(NativeDBType.R4 == DbType, "Value_R4");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteSingle(ValueOffset, value);
        }

        private double Value_R8()
        {
            Debug.Assert(NativeDBType.R8 == DbType, "Value_R8");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_R8");

            return RowBinding.ReadDouble(ValueOffset);
        }
        private void Value_R8(double value)
        {
            Debug.Assert(NativeDBType.R8 == DbType, "Value_I4");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteDouble(ValueOffset, value);
        }

        private byte Value_UI1()
        {
            Debug.Assert(NativeDBType.UI1 == DbType, "Value_UI1");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_UI1");
            return RowBinding.ReadByte(ValueOffset);
        }
        private void Value_UI1(byte value)
        {
            Debug.Assert(NativeDBType.UI1 == DbType, "Value_UI1");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteByte(ValueOffset, value);
        }

        internal ushort Value_UI2()
        {
            Debug.Assert(NativeDBType.UI2 == DbType, "Value_UI2");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_UI2");
            return unchecked((ushort)RowBinding.ReadInt16(ValueOffset));
        }
        private void Value_UI2(ushort value)
        {
            Debug.Assert(NativeDBType.UI2 == DbType, "Value_UI2");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt16(ValueOffset, unchecked((short)value));
        }

        internal uint Value_UI4()
        {
            Debug.Assert(NativeDBType.UI4 == DbType, "Value_UI4");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_UI4");
            return unchecked((uint)RowBinding.ReadInt32(ValueOffset));
        }
        private void Value_UI4(uint value)
        {
            Debug.Assert(NativeDBType.UI4 == DbType, "Value_UI4");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt32(ValueOffset, unchecked((int)value));
        }

        internal ulong Value_UI8()
        {
            Debug.Assert(NativeDBType.UI8 == DbType, "Value_UI8");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_UI8");
            return unchecked((ulong)RowBinding.ReadInt64(ValueOffset));
        }
        private void Value_UI8(ulong value)
        {
            Debug.Assert(NativeDBType.UI8 == DbType, "Value_UI8");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.WriteInt64(ValueOffset, unchecked((long)value));
        }

        private string Value_WSTR()
        {
            Debug.Assert(NativeDBType.WSTR == DbType, "Value_WSTR");
            Debug.Assert((DBStatus.S_OK == StatusValue()) || (DBStatus.S_TRUNCATED == StatusValue()), "Value_WSTR");
            Debug.Assert(2 < ColumnBindingMaxLen, "Value_WSTR");
            int byteCount = Math.Min(LengthValue(), ColumnBindingMaxLen - 2);
            return RowBinding.PtrToStringUni(ValueOffset, byteCount / 2);
        }
        private void Value_WSTR(string value)
        {
            Debug.Assert(null != value, "Value_BYTES null");
            Debug.Assert(NativeDBType.WSTR == DbType, "Value_WSTR");
            // we silently truncate when the user has specified a given Size
            int charCount = ((ValueBindingOffset < value.Length) ? Math.Min(value.Length - ValueBindingOffset, (ColumnBindingMaxLen - 2) / 2) : 0);

            LengthValue(charCount * 2);
            StatusValue(DBStatus.S_OK);
            if (0 < charCount)
            {
                char[] chars = value.ToCharArray(ValueBindingOffset, charCount);
                RowBinding.WriteCharArray(ValueOffset, chars, ValueBindingOffset, charCount);
            }
        }
        private void Value_WSTR(char[] value)
        {
            Debug.Assert(null != value, "Value_BYTES null");
            Debug.Assert(NativeDBType.WSTR == DbType, "Value_WSTR");
            // we silently truncate when the user has specified a given Size
            int charCount = ((ValueBindingOffset < value.Length) ? Math.Min(value.Length - ValueBindingOffset, (ColumnBindingMaxLen - 2) / 2) : 0);

            LengthValue(charCount * 2);
            StatusValue(DBStatus.S_OK);
            if (0 < charCount)
            {
                RowBinding.WriteCharArray(ValueOffset, value, ValueBindingOffset, charCount);
            }
        }
        private object Value_VARIANT()
        {
            Debug.Assert((NativeDBType.VARIANT == DbType) || (NativeDBType.PROPVARIANT == DbType), "Value_VARIANT");
            Debug.Assert(DBStatus.S_OK == StatusValue(), "Value_VARIANT");
            return RowBinding.GetVariantValue(ValueOffset);
        }
        private void Value_VARIANT(object value)
        {
            Debug.Assert((NativeDBType.VARIANT == DbType) || (NativeDBType.PROPVARIANT == DbType), "Value_VARIANT");
            LengthValue(0);
            StatusValue(DBStatus.S_OK);
            RowBinding.SetVariantValue(ValueOffset, value);
        }

        internal bool ValueBoolean()
        {
            bool value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.BOOL:
                            value = Value_BOOL();
                            break;
                        case NativeDBType.VARIANT:
                            value = (bool)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(bool));
            }
            return value;
        }

        internal byte[] ValueByteArray()
        {
            byte[]? value = (byte[]?)_value;
            if (null == value)
            {
                switch (StatusValue())
                {
                    case DBStatus.S_OK:
                        switch (DbType)
                        {
                            case NativeDBType.BYTES:
                                value = Value_BYTES(); // String
                                break;
                            case NativeDBType.VARIANT:
                                value = (byte[])ValueVariant(); // Object
                                break;
                            case (NativeDBType.BYREF | NativeDBType.BYTES):
                                value = Value_ByRefBYTES();
                                break;
                            default:
                                throw ODB.ConversionRequired();
                        }
                        break;
                    case DBStatus.S_TRUNCATED:
                        switch (DbType)
                        {
                            case NativeDBType.BYTES:
                                value = Value_BYTES();
                                break;
                            case (NativeDBType.BYREF | NativeDBType.BYTES):
                                value = Value_ByRefBYTES();
                                break;
                            default:
                                throw ODB.ConversionRequired();
                        }
                        break;
                    default:
                        throw CheckTypeValueStatusValue(typeof(byte[]));
                }
                _value = value;
            }
            return value;
        }

        internal byte ValueByte()
        {
            byte value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.UI1:
                            value = Value_UI1();
                            break;
                        case NativeDBType.VARIANT:
                            value = (byte)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(byte));
            }
            return value;
        }

        internal OleDbDataReader ValueChapter()
        {
            OleDbDataReader? value = (OleDbDataReader?)_value;
            if (null == value)
            {
                switch (StatusValue())
                {
                    case DBStatus.S_OK:
                        switch (DbType)
                        {
                            case NativeDBType.HCHAPTER:
                                value = Value_HCHAPTER(); // OleDbDataReader
                                break;
                            default:
                                throw ODB.ConversionRequired();
                        }
                        break;
                    default:
                        throw CheckTypeValueStatusValue(typeof(string));
                }
                _value = value;
            }
            return value;
        }

        internal DateTime ValueDateTime()
        {
            DateTime value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.DATE:
                            value = Value_DATE();
                            break;
                        case NativeDBType.DBDATE:
                            value = Value_DBDATE();
                            break;
                        case NativeDBType.DBTIMESTAMP:
                            value = Value_DBTIMESTAMP();
                            break;
                        case NativeDBType.FILETIME:
                            value = Value_FILETIME();
                            break;
                        case NativeDBType.VARIANT:
                            value = (DateTime)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(short));
            }
            return value;
        }

        internal decimal ValueDecimal()
        {
            decimal value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.CY:
                            value = Value_CY();
                            break;
                        case NativeDBType.DECIMAL:
                            value = Value_DECIMAL();
                            break;
                        case NativeDBType.NUMERIC:
                            value = Value_NUMERIC();
                            break;
                        case NativeDBType.UI8:
                            value = (decimal)Value_UI8();
                            break;
                        case NativeDBType.VARIANT:
                            value = (decimal)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(short));
            }
            return value;
        }

        internal Guid ValueGuid()
        {
            Guid value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.GUID:
                            value = Value_GUID();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(short));
            }
            return value;
        }

        internal short ValueInt16()
        {
            short value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.I2:
                            value = Value_I2();
                            break;
                        case NativeDBType.I1:
                            value = (short)Value_I1();
                            break;
                        case NativeDBType.VARIANT:
                            object variant = ValueVariant();
                            if (variant is sbyte)
                            {
                                value = (short)(sbyte)variant;
                            }
                            else
                            {
                                value = (short)variant;
                            }
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(short));
            }
            return value;
        }

        internal int ValueInt32()
        {
            int value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.I4:
                            value = Value_I4();
                            break;
                        case NativeDBType.UI2:
                            value = (int)Value_UI2();
                            break;
                        case NativeDBType.VARIANT:
                            object variant = ValueVariant();
                            if (variant is ushort)
                            {
                                value = (int)(ushort)variant;
                            }
                            else
                            {
                                value = (int)variant;
                            }
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(int));
            }
            return value;
        }

        internal long ValueInt64()
        {
            long value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.I8:
                            value = Value_I8();
                            break;
                        case NativeDBType.UI4:
                            value = (long)Value_UI4();
                            break;
                        case NativeDBType.VARIANT:
                            object variant = ValueVariant();
                            if (variant is uint)
                            {
                                value = (long)(uint)variant;
                            }
                            else
                            {
                                value = (long)variant;
                            }
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(long));
            }
            return value;
        }

        internal float ValueSingle()
        {
            float value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.R4:
                            value = Value_R4();
                            break;
                        case NativeDBType.VARIANT:
                            value = (float)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(float));
            }
            return value;
        }

        internal double ValueDouble()
        {
            double value;
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    switch (DbType)
                    {
                        case NativeDBType.R8:
                            value = Value_R8();
                            break;
                        case NativeDBType.VARIANT:
                            value = (double)ValueVariant();
                            break;
                        default:
                            throw ODB.ConversionRequired();
                    }
                    break;
                default:
                    throw CheckTypeValueStatusValue(typeof(double));
            }
            return value;
        }

        internal string ValueString()
        {
            string? value = (string?)_value;
            if (null == value)
            {
                switch (StatusValue())
                {
                    case DBStatus.S_OK:
                        switch (DbType)
                        {
                            case NativeDBType.BSTR:
                                value = Value_BSTR(); // String
                                break;
                            case NativeDBType.VARIANT:
                                value = (string)ValueVariant(); // Object
                                break;
                            case NativeDBType.WSTR:
                                value = Value_WSTR(); // String
                                break;
                            case (NativeDBType.BYREF | NativeDBType.WSTR):
                                value = Value_ByRefWSTR();
                                break;
                            default:
                                throw ODB.ConversionRequired();
                        }
                        break;
                    case DBStatus.S_TRUNCATED:
                        switch (DbType)
                        {
                            case NativeDBType.WSTR:
                                value = Value_WSTR();
                                break;
                            case (NativeDBType.BYREF | NativeDBType.WSTR):
                                value = Value_ByRefWSTR();
                                break;
                            default:
                                throw ODB.ConversionRequired();
                        }
                        break;
                    default:
                        throw CheckTypeValueStatusValue(typeof(string));
                }
                _value = value;
            }
            return value;
        }

        private object ValueVariant()
        {
            object? value = _value;
            if (null == value)
            {
                value = Value_VARIANT();
                _value = value;
            }
            return value;
        }

        private Exception CheckTypeValueStatusValue()
        {
            return CheckTypeValueStatusValue(ExpectedType);
        }

        private Exception CheckTypeValueStatusValue(Type expectedType)
        {
            switch (StatusValue())
            {
                case DBStatus.S_OK:
                    Debug.Fail("CheckStatusValue: unhandled data with ok status");
                    goto case DBStatus.E_CANTCONVERTVALUE;
                case DBStatus.S_TRUNCATED:
                    Debug.Fail("CheckStatusValue: unhandled data with truncated status");
                    goto case DBStatus.E_CANTCONVERTVALUE;
                case DBStatus.E_BADACCESSOR:
                    return ODB.BadAccessor();
                case DBStatus.E_CANTCONVERTVALUE:
                    return ODB.CantConvertValue(); // UNDONE: need original data type
                case DBStatus.S_ISNULL: // database null
                    return ADP.InvalidCast(); // UNDONE: NullValue exception
                case DBStatus.E_SIGNMISMATCH:
                    return ODB.SignMismatch(expectedType);
                case DBStatus.E_DATAOVERFLOW:
                    return ODB.DataOverflow(expectedType);
                case DBStatus.E_CANTCREATE:
                    return ODB.CantCreate(expectedType);
                case DBStatus.E_UNAVAILABLE:
                    return ODB.Unavailable(expectedType);
                default:
                    return ODB.UnexpectedStatusValue(StatusValue());
            }
        }
    }
}