File: System\Data\OleDb\RowBinding.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="RowBinding.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data.OleDb {
 
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Data;
    using System.Data.Common;
    using System.Globalization;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Text;
    using System.Threading;
    using System.Runtime.ConstrainedExecution;
 
    sealed internal class RowBinding : System.Data.ProviderBase.DbBuffer {
        private readonly int _bindingCount;
        private readonly int _headerLength;
        private readonly int _dataLength;
        private readonly int _emptyStringOffset;
 
        private UnsafeNativeMethods.IAccessor _iaccessor;
        private IntPtr _accessorHandle;
 
        private readonly bool _needToReset;
        private bool _haveData;
 
        // tagDBBINDING[] starting 64bit aligned
        // all DBSTATUS values (32bit per value), starting 64bit aligned
        // all DBLENGTH values (32/64bit per value), starting 64bit alignedsa
        // all data values listed after that (variable length), each individual starting 64bit aligned
        // Int64 - zero for pointers to emptystring
 
        internal static RowBinding CreateBuffer(int bindingCount, int databuffersize, bool needToReset) {
            int headerLength = RowBinding.AlignDataSize(bindingCount * ODB.SizeOf_tagDBBINDING);
            int length = RowBinding.AlignDataSize(headerLength + databuffersize) + 8; // 8 bytes for a null terminated string
            return new RowBinding(bindingCount, headerLength, databuffersize, length, needToReset);
        }
 
        private RowBinding(int bindingCount, int headerLength, int dataLength, int length, bool needToReset) : base(length) {
            _bindingCount = bindingCount;
            _headerLength = headerLength;
            _dataLength = dataLength;
            _emptyStringOffset = length - 8; // 8 bytes for a null terminated string
            _needToReset = needToReset;
 
            Debug.Assert(0 < _bindingCount, "bad _bindingCount");
            Debug.Assert(0 < _headerLength, "bad _headerLength");
            Debug.Assert(0 < _dataLength, "bad _dataLength");
            Debug.Assert(_bindingCount * 3 * IntPtr.Size <= _dataLength, "_dataLength too small");
            Debug.Assert(_headerLength + _dataLength <= _emptyStringOffset, "bad string offset");
            Debug.Assert(_headerLength + _dataLength + 8 <= length, "bad length");
        }
 
        internal void StartDataBlock() {
            if (_haveData) {
                Debug.Assert(false, "previous row not yet cleared");
                ResetValues();
            }
            _haveData = true;
        }
 
        internal int BindingCount() {
            return _bindingCount;
        }
 
        internal IntPtr DangerousGetAccessorHandle() {
            return _accessorHandle;
        }
 
        internal IntPtr DangerousGetDataPtr() {
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // NOTE: You must have called DangerousAddRef before calling this
            //       method, or you run the risk of allowing Handle Recycling
            //       to occur!
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            return ADP.IntPtrOffset(DangerousGetHandle(), _headerLength);
        }
 
        internal IntPtr DangerousGetDataPtr(int valueOffset) {
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // NOTE: You must have called DangerousAddRef before calling this
            //       method, or you run the risk of allowing Handle Recycling
            //       to occur!
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            return ADP.IntPtrOffset(DangerousGetHandle(), valueOffset);
        }
 
        internal OleDbHResult CreateAccessor(UnsafeNativeMethods.IAccessor iaccessor, int flags, ColumnBinding[] bindings) {
            OleDbHResult hr = 0;
            int[] rowBindStatus = new int[BindingCount()];
 
            _iaccessor = iaccessor;
 
            Bid.Trace("<oledb.IAccessor.CreateAccessor|API|OLEDB>\n");
            hr = iaccessor.CreateAccessor(flags, (IntPtr)rowBindStatus.Length, this, (IntPtr)_dataLength, out _accessorHandle, rowBindStatus); // MDAC 69530
            Bid.Trace("<oledb.IAccessor.CreateAccessor|API|OLEDB|RET> %08X{HRESULT}\n", hr);
 
            for (int k = 0; k < rowBindStatus.Length; ++k) {
                if (DBBindStatus.OK != (DBBindStatus)rowBindStatus[k]) {
                    if (ODB.DBACCESSOR_PARAMETERDATA == flags) {
                        throw ODB.BadStatus_ParamAcc(bindings[k].ColumnBindingOrdinal, (DBBindStatus)rowBindStatus[k]);
                    }
                    else if (ODB.DBACCESSOR_ROWDATA == flags) {
                        throw ODB.BadStatusRowAccessor(bindings[k].ColumnBindingOrdinal, (DBBindStatus)rowBindStatus[k]);
                    }
                    else Debug.Assert(false, "unknown accessor buffer");
                }
            }
            return hr;
        }
 
        internal ColumnBinding[] SetBindings(OleDbDataReader dataReader, Bindings bindings,
                                             int indexStart, int indexForAccessor,
                                             OleDbParameter[] parameters, tagDBBINDING[] dbbindings, bool ifIRowsetElseIRow) {
            Debug.Assert(null != bindings, "null bindings");
            Debug.Assert(dbbindings.Length == BindingCount(), "count mismatch");
 
            bool mustRelease = false;
 
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
 
                IntPtr buffer = DangerousGetHandle();
                for(int i = 0; i < dbbindings.Length; ++i) {
                    IntPtr ptr = ADP.IntPtrOffset(buffer, (i * ODB.SizeOf_tagDBBINDING));
                    Marshal.StructureToPtr(dbbindings[i], ptr, false/*deleteold*/);
                }
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
 
            ColumnBinding[] columns = new ColumnBinding[dbbindings.Length];
            for(int indexWithinAccessor = 0; indexWithinAccessor < columns.Length; ++indexWithinAccessor) {
                int index = indexStart + indexWithinAccessor;
                OleDbParameter parameter = ((null != parameters) ? parameters[index] : null);
                columns[indexWithinAccessor] = new ColumnBinding(
                    dataReader, index, indexForAccessor, indexWithinAccessor,
                    parameter, this, bindings, dbbindings[indexWithinAccessor], _headerLength,
                    ifIRowsetElseIRow);
            }
            return columns;
        }
 
        static internal int AlignDataSize(int value) {
            // buffer data to start on 8-byte boundary
            return Math.Max(8, (value + 7) & ~0x7); // MDAC 70350
        }
 
        internal object GetVariantValue(int offset) {
            Debug.Assert(_needToReset, "data type requires reseting and _needToReset is false");
            Debug.Assert(0 == (ODB.SizeOf_Variant % 8), "unexpected VARIANT size mutiplier");
            Debug.Assert(0 == offset%8, "invalid alignment");
            ValidateCheck(offset, 2*ODB.SizeOf_Variant);
 
            object value = null;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
 
                IntPtr buffer = ADP.IntPtrOffset(DangerousGetHandle(), offset);
                value = Marshal.GetObjectForNativeVariant(buffer);
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
 
            return ((null != value) ? value : DBNull.Value);
        }
 
        // translate to native
        internal void SetVariantValue(int offset, object value) {
            // two contigous VARIANT structures, second should be a binary copy of the first
            Debug.Assert(_needToReset, "data type requires reseting and _needToReset is false");
            Debug.Assert(0 == (ODB.SizeOf_Variant % 8), "unexpected VARIANT size mutiplier");
            Debug.Assert(0 == offset%8, "invalid alignment");
            ValidateCheck(offset, 2*ODB.SizeOf_Variant);
 
            IntPtr buffer = ADP.PtrZero;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
                
                buffer = ADP.IntPtrOffset(DangerousGetHandle(), offset);
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    // GetNativeVariantForObject must be in try block since it has no reliability contract
                    Marshal.GetNativeVariantForObject(value, buffer);
                }
                finally {
                    // safe to copy memory(dst,src,count), even if GetNativeVariantForObject failed
                    NativeOledbWrapper.MemoryCopy(ADP.IntPtrOffset(buffer,ODB.SizeOf_Variant), buffer, ODB.SizeOf_Variant);
                }
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
        }
 
        // value
        // cached value
        // cached zero value
        // translate to native
        internal void SetBstrValue(int offset, string value) {
            // two contigous BSTR ptr, second should be a binary copy of the first
            Debug.Assert(_needToReset, "data type requires reseting and _needToReset is false");
            Debug.Assert(0 == offset%ADP.PtrSize, "invalid alignment");
            ValidateCheck(offset, 2*IntPtr.Size);
 
            IntPtr ptr;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try { } finally {
                    ptr = SafeNativeMethods.SysAllocStringLen(value, value.Length);
 
                    // safe to copy ptr, even if SysAllocStringLen failed
                    Marshal.WriteIntPtr(base.handle, offset, ptr);
                    Marshal.WriteIntPtr(base.handle, offset + ADP.PtrSize, ptr);
                }
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
            if (IntPtr.Zero == ptr) {
                throw new OutOfMemoryException();
            }
        }
 
        // translate to native
        internal void SetByRefValue(int offset, IntPtr pinnedValue) {
            Debug.Assert(_needToReset, "data type requires reseting and _needToReset is false");
            Debug.Assert(0 == offset%ADP.PtrSize, "invalid alignment");
            ValidateCheck(offset, 2*IntPtr.Size);
 
            if (ADP.PtrZero == pinnedValue) { // empty array scenario
                pinnedValue = ADP.IntPtrOffset(base.handle, _emptyStringOffset);
            }
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try { } finally {
                    Marshal.WriteIntPtr(base.handle, offset, pinnedValue);               // parameter input value
                    Marshal.WriteIntPtr(base.handle, offset + ADP.PtrSize, pinnedValue); // original parameter value
                }
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
        }
 
        internal void CloseFromConnection() {
            _iaccessor = null;
            _accessorHandle = ODB.DB_INVALID_HACCESSOR;
        }
 
        internal new void Dispose() {
            ResetValues();
 
            UnsafeNativeMethods.IAccessor iaccessor = _iaccessor;
            IntPtr accessorHandle = _accessorHandle;
 
            _iaccessor = null;
            _accessorHandle = ODB.DB_INVALID_HACCESSOR;
 
            if ((ODB.DB_INVALID_HACCESSOR != accessorHandle) && (null != iaccessor)) {
                OleDbHResult hr;
                int refcount;
                hr = iaccessor.ReleaseAccessor(accessorHandle, out refcount);
                if (hr < 0) { // ignore any error msgs
                    SafeNativeMethods.Wrapper.ClearErrorInfo();
                }
            }
 
            base.Dispose();
        }
 
        internal void ResetValues() {
            if (_needToReset && _haveData) {
                lock(this) { // prevent Dispose/ResetValues race condition
 
                    bool mustRelease = false;
                    RuntimeHelpers.PrepareConstrainedRegions();
                    try {
                        DangerousAddRef(ref mustRelease);
 
                        ResetValues(DangerousGetHandle(), _iaccessor);
                    }
                    finally {
                        if (mustRelease) {
                            DangerousRelease();
                        }
                    }
                }
            }
            else {
                _haveData = false;
            }
#if DEBUG
            // verify types that need reseting are not forgotton, since the code
            // that sets this up is in dbbinding.cs, MaxLen { set; }
            if (!_needToReset) {
                Debug.Assert(0 <= _bindingCount && (_bindingCount * ODB.SizeOf_tagDBBINDING) < Length, "bad _bindingCount");
                for (int i = 0; i < _bindingCount; ++i) {
                    short wtype = ReadInt16((i * ODB.SizeOf_tagDBBINDING) + ODB.OffsetOf_tagDBBINDING_wType);
                    switch(wtype) {
                    case (NativeDBType.BYREF | NativeDBType.BYTES):
                    case (NativeDBType.BYREF | NativeDBType.WSTR):
                    case NativeDBType.PROPVARIANT:
                    case NativeDBType.VARIANT:
                    case NativeDBType.BSTR:
                    case NativeDBType.HCHAPTER:
                        Debug.Assert(false, "expected _needToReset");
                        break;
                    }
                }
            }
#endif
        }
 
        // requires ReliabilityContract to be called by ReleaseHandle
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        private void ResetValues(IntPtr buffer, object iaccessor) {
            Debug.Assert(ADP.PtrZero != buffer && _needToReset && _haveData, "shouldn't be calling ResetValues");
            for (int i = 0; i < _bindingCount; ++i) {
                IntPtr ptr = ADP.IntPtrOffset(buffer, (i * ODB.SizeOf_tagDBBINDING));
 
                int valueOffset = _headerLength + Marshal.ReadIntPtr(ptr, ODB.OffsetOf_tagDBBINDING_obValue).ToInt32();
                short wtype = Marshal.ReadInt16(ptr, ODB.OffsetOf_tagDBBINDING_wType);
 
                switch(wtype) {
                case (NativeDBType.BYREF | NativeDBType.BYTES):
                case (NativeDBType.BYREF | NativeDBType.WSTR):
                    ValidateCheck(valueOffset, 2*IntPtr.Size);
                    FreeCoTaskMem(buffer, valueOffset);
                    break;
                case NativeDBType.PROPVARIANT:
                    ValidateCheck(valueOffset, 2*NativeOledbWrapper.SizeOfPROPVARIANT);
                    FreePropVariant(buffer, valueOffset);
                    break;
                case NativeDBType.VARIANT:
                    ValidateCheck(valueOffset, 2*ODB.SizeOf_Variant);
                    FreeVariant(buffer, valueOffset);
                    break;
                case NativeDBType.BSTR:
                    ValidateCheck(valueOffset, 2*IntPtr.Size);
                    FreeBstr(buffer, valueOffset);
                    break;
                case NativeDBType.HCHAPTER:
                    if (null != iaccessor) {
                        // iaccessor will always be null when from ReleaseHandle
                        FreeChapter(buffer, valueOffset, iaccessor);
                    }
                    break;
#if DEBUG

                case NativeDBType.EMPTY:
                case NativeDBType.NULL:
                case NativeDBType.I2:
                case NativeDBType.I4:
                case NativeDBType.R4:
                case NativeDBType.R8:
                case NativeDBType.CY:
                case NativeDBType.DATE:
                case NativeDBType.ERROR:
                case NativeDBType.BOOL:
                case NativeDBType.DECIMAL:
                case NativeDBType.I1:
                case NativeDBType.UI1:
                case NativeDBType.UI2:
                case NativeDBType.UI4:
                case NativeDBType.I8:
                case NativeDBType.UI8:
                case NativeDBType.FILETIME:
                case NativeDBType.GUID:
                case NativeDBType.BYTES:
                case NativeDBType.WSTR:
                case NativeDBType.NUMERIC:
                case NativeDBType.DBDATE:
                case NativeDBType.DBTIME:
                case NativeDBType.DBTIMESTAMP:
                    break; // known, do nothing
                case NativeDBType.IDISPATCH:
                case NativeDBType.IUNKNOWN:
                    break; // known, releasing RowHandle will handle lifetimes correctly
                default:
                    Debug.Assert(false, "investigate");
                    break;
#endif
                }
            }
            _haveData = false;
        }
 
        // this correctly does not have a ReliabilityContract, will not be called via ReleaseHandle
        static private void FreeChapter(IntPtr buffer, int valueOffset, object iaccessor) {
            Debug.Assert (0 == valueOffset % 8, "unexpected unaligned ptr offset");
 
            UnsafeNativeMethods.IChapteredRowset chapteredRowset = (iaccessor as UnsafeNativeMethods.IChapteredRowset);
            IntPtr chapter = SafeNativeMethods.InterlockedExchangePointer(ADP.IntPtrOffset(buffer, valueOffset), ADP.PtrZero);
            if (ODB.DB_NULL_HCHAPTER != chapter) {
                int refCount;
                Bid.Trace("<oledb.IChapteredRowset.ReleaseChapter|API|OLEDB> Chapter=%Id\n", chapter);
                OleDbHResult hr = chapteredRowset.ReleaseChapter(chapter, out refCount);
                Bid.Trace("<oledb.IChapteredRowset.ReleaseChapter|API|OLEDB|RET> %08X{HRESULT}, RefCount=%d\n", hr, refCount);
            }
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        static private void FreeBstr(IntPtr buffer, int valueOffset) {
            Debug.Assert (0 == valueOffset % 8, "unexpected unaligned ptr offset");
 
            // two contigous BSTR ptrs that need to be freed
            // the second should only be freed if different from the first
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                IntPtr currentValue = Marshal.ReadIntPtr(buffer, valueOffset);
                IntPtr originalValue = Marshal.ReadIntPtr(buffer, valueOffset + ADP.PtrSize);
 
                if ((ADP.PtrZero != currentValue) && (currentValue != originalValue)) {
                    SafeNativeMethods.SysFreeString(currentValue);
                }
                if (ADP.PtrZero != originalValue) {
                    SafeNativeMethods.SysFreeString(originalValue);
                }
 
                // for debugability - delay clearing memory until after FreeBSTR
                Marshal.WriteIntPtr(buffer, valueOffset, ADP.PtrZero);
                Marshal.WriteIntPtr(buffer, valueOffset + ADP.PtrSize, ADP.PtrZero);
            }
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        static private void FreeCoTaskMem(IntPtr buffer, int valueOffset) {
            Debug.Assert (0 == valueOffset % 8, "unexpected unaligned ptr offset");
 
            // two contigous CoTaskMemAlloc ptrs that need to be freed
            // the first should only be freed if different from the first
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                IntPtr currentValue = Marshal.ReadIntPtr(buffer, valueOffset);
                IntPtr originalValue = Marshal.ReadIntPtr(buffer, valueOffset + ADP.PtrSize);
 
                // originalValue is pinned managed memory or pointer to emptyStringOffset
                if ((ADP.PtrZero != currentValue) && (currentValue != originalValue)) {
                    SafeNativeMethods.CoTaskMemFree(currentValue);
                }
 
                // for debugability - delay clearing memory until after CoTaskMemFree
                Marshal.WriteIntPtr(buffer, valueOffset, ADP.PtrZero);
                Marshal.WriteIntPtr(buffer, valueOffset + ADP.PtrSize, ADP.PtrZero);
            }
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        static private void FreeVariant(IntPtr buffer, int valueOffset) {
            // two contigous VARIANT structures that need to be freed
            // the second should only be freed if different from the first
 
            Debug.Assert(0 == (ODB.SizeOf_Variant % 8), "unexpected VARIANT size mutiplier");
            Debug.Assert (0 == valueOffset % 8, "unexpected unaligned ptr offset");
 
            IntPtr currentHandle = ADP.IntPtrOffset(buffer, valueOffset);
            IntPtr originalHandle = ADP.IntPtrOffset(buffer, valueOffset+ODB.SizeOf_Variant);
            bool different = NativeOledbWrapper.MemoryCompare(currentHandle, originalHandle, ODB.SizeOf_Variant);
 
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                // always clear the first structure
                SafeNativeMethods.VariantClear(currentHandle);
                if (different) {
                    // second structure different from the first
                    SafeNativeMethods.VariantClear(originalHandle);
                }
                else {
                    // second structure same as the first, just clear the field
                    SafeNativeMethods.ZeroMemory(originalHandle, (IntPtr)ODB.SizeOf_Variant);
                }
            }
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        static private void FreePropVariant(IntPtr buffer, int valueOffset) {
            // two contigous PROPVARIANT structures that need to be freed
            // the second should only be freed if different from the first
            Debug.Assert(0 == (NativeOledbWrapper.SizeOfPROPVARIANT % 8), "unexpected PROPVARIANT size mutiplier");
            Debug.Assert (0 == valueOffset % 8, "unexpected unaligned ptr offset");
 
            IntPtr currentHandle = ADP.IntPtrOffset(buffer, valueOffset);
            IntPtr originalHandle = ADP.IntPtrOffset(buffer, valueOffset+NativeOledbWrapper.SizeOfPROPVARIANT);
            bool different = NativeOledbWrapper.MemoryCompare(currentHandle, originalHandle, NativeOledbWrapper.SizeOfPROPVARIANT);
 
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                // always clear the first structure
                SafeNativeMethods.PropVariantClear(currentHandle);
                if (different) {
                    // second structure different from the first
                    SafeNativeMethods.PropVariantClear(originalHandle);
                }
                else {
                    // second structure same as the first, just clear the field
                    SafeNativeMethods.ZeroMemory(originalHandle, (IntPtr)NativeOledbWrapper.SizeOfPROPVARIANT);
                }
            }
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        internal IntPtr InterlockedExchangePointer(int offset) {
            ValidateCheck(offset, IntPtr.Size);
            Debug.Assert(0 == offset%ADP.PtrSize, "invalid alignment");
 
            IntPtr value;
            bool   mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
 
                IntPtr ptr = ADP.IntPtrOffset(DangerousGetHandle(), offset);
                value = SafeNativeMethods.InterlockedExchangePointer(ptr, IntPtr.Zero);
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
            return value;
        }
        
        override protected bool ReleaseHandle() {
            // NOTE: The SafeHandle class guarantees this will be called exactly once.
            _iaccessor = null;
            if (_needToReset && _haveData) {
                IntPtr buffer = base.handle;
                if (IntPtr.Zero != buffer) {
                    ResetValues(buffer, null);
                }
            }
            return base.ReleaseHandle();
        }
    }
}