File: System\Data\Common\DataStorage.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="DataStorage.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.Common {
    using System;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Data.SqlTypes;
    using System.Diagnostics;
    using System.Xml;
    using System.Xml.Serialization;
 
    internal enum StorageType {
        Empty       = TypeCode.Empty, // 0
        Object      = TypeCode.Object,
        DBNull      = TypeCode.DBNull,
        Boolean     = TypeCode.Boolean,
        Char        = TypeCode.Char,
        SByte       = TypeCode.SByte,
        Byte        = TypeCode.Byte,
        Int16       = TypeCode.Int16,
        UInt16      = TypeCode.UInt16,
        Int32       = TypeCode.Int32,
        UInt32      = TypeCode.UInt32,
        Int64       = TypeCode.Int64,
        UInt64      = TypeCode.UInt64,
        Single      = TypeCode.Single,
        Double      = TypeCode.Double,
        Decimal     = TypeCode.Decimal, // 15
        DateTime    = TypeCode.DateTime, // 16
        TimeSpan    = 17,
        String      = TypeCode.String, // 18
        Guid        = 19,
 
        ByteArray   = 20,
        CharArray   = 21,
        Type        = 22,
        DateTimeOffset = 23,
        BigInteger  = 24,
        Uri         = 25,
 
        SqlBinary, // SqlTypes should remain at the end for IsSqlType checking
        SqlBoolean,
        SqlByte,
        SqlBytes,
        SqlChars,
        SqlDateTime,
        SqlDecimal,
        SqlDouble,
        SqlGuid,
        SqlInt16,
        SqlInt32,
        SqlInt64,
        SqlMoney,
        SqlSingle,
        SqlString,
//        SqlXml,
    };
 
    abstract internal class DataStorage {
 
        // for Whidbey 40426, searching down the Type[] is about 20% faster than using a Dictionary
        // must keep in same order as enum StorageType
        private static readonly Type[] StorageClassType = new Type[] {
            null,
            typeof(Object),
            typeof(DBNull),
            typeof(Boolean),
            typeof(Char),
            typeof(SByte),
            typeof(Byte),
            typeof(Int16),
            typeof(UInt16),
            typeof(Int32),
            typeof(UInt32),
            typeof(Int64),
            typeof(UInt64),
            typeof(Single),
            typeof(Double),
            typeof(Decimal),
            typeof(DateTime),
            typeof(TimeSpan),
            typeof(String),
            typeof(Guid),
 
            typeof(byte[]),
            typeof(char[]),
            typeof(Type),
            typeof(DateTimeOffset),            
            typeof(System.Numerics.BigInteger),
            typeof(Uri),
 
            typeof(SqlBinary),
            typeof(SqlBoolean),
            typeof(SqlByte),
            typeof(SqlBytes),
            typeof(SqlChars),
            typeof(SqlDateTime),
            typeof(SqlDecimal),
            typeof(SqlDouble),
            typeof(SqlGuid),
            typeof(SqlInt16),
            typeof(SqlInt32),
            typeof(SqlInt64),
            typeof(SqlMoney),
            typeof(SqlSingle),
            typeof(SqlString),
//            typeof(SqlXml),
        };
 
        internal readonly DataColumn Column;
        internal readonly DataTable Table;
        internal readonly Type DataType;
        internal readonly StorageType StorageTypeCode;
        private System.Collections.BitArray dbNullBits;
 
        private readonly object DefaultValue;
        internal readonly object NullValue;
 
        internal readonly bool IsCloneable;
        internal readonly bool IsCustomDefinedType;
        internal readonly bool IsStringType;
        internal readonly bool IsValueType;
 
        private readonly static Func<Type, Tuple<bool, bool, bool, bool>> _inspectTypeForInterfaces = InspectTypeForInterfaces;
        private readonly static ConcurrentDictionary<Type, Tuple<bool, bool, bool, bool>> _typeImplementsInterface = new ConcurrentDictionary<Type, Tuple<bool, bool, bool, bool>>();
 
        protected DataStorage(DataColumn column, Type type, object defaultValue, StorageType storageType)
            : this(column, type, defaultValue, DBNull.Value, false, storageType) {
        }
 
        protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, StorageType storageType)
            : this(column, type, defaultValue, nullValue, false, storageType) {
        }
 
        protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, bool isICloneable, StorageType storageType) {
            Debug.Assert(storageType == GetStorageType(type), "Incorrect storage type specified");
            Column = column;
            Table = column.Table;
            DataType = type;
            StorageTypeCode = storageType;
            DefaultValue = defaultValue;
            NullValue = nullValue;
            IsCloneable = isICloneable;
            IsCustomDefinedType = IsTypeCustomType(StorageTypeCode);
            IsStringType = ((StorageType.String == StorageTypeCode) || (StorageType.SqlString == StorageTypeCode));
            IsValueType = DetermineIfValueType(StorageTypeCode, type);
        }
 
        internal DataSetDateTime DateTimeMode {
            get {
                return Column.DateTimeMode;
            }
        }
 
        internal IFormatProvider FormatProvider {
            get {
                return Table.FormatProvider;
            }
        }
 
        public virtual Object Aggregate(int[] recordNos, AggregateType kind) {
            if (AggregateType.Count == kind) {
                return this.AggregateCount(recordNos);
            }
            return null;
        }
 
        public object AggregateCount(int[] recordNos) {
            int count = 0;
            for (int i = 0; i < recordNos.Length; i++) {
                if (!this.dbNullBits.Get(recordNos[i]))
                    count++;
            }
            return count;
        }
 
        protected int CompareBits(int recordNo1, int recordNo2) {
            bool recordNo1Null = this.dbNullBits.Get(recordNo1);
            bool recordNo2Null = this.dbNullBits.Get(recordNo2);
            if (recordNo1Null ^ recordNo2Null) {
                if (recordNo1Null)
                    return -1;
                else
                    return 1;
            }
 
            return 0;
        }
 
        public abstract int Compare(int recordNo1, int recordNo2);
 
        // only does comparision, expect value to be of the correct type
        public abstract int CompareValueTo(int recordNo1, object value);
 
        // only does conversion with support for reference null
        public virtual object ConvertValue(object value) {
            return value;
        }
 
        protected void CopyBits(int srcRecordNo, int dstRecordNo) {
            this.dbNullBits.Set(dstRecordNo, this.dbNullBits.Get(srcRecordNo));
        }
 
        abstract public void Copy(int recordNo1, int recordNo2);
 
        abstract public Object Get(int recordNo);
 
        protected Object GetBits(int recordNo) {
            if (this.dbNullBits.Get(recordNo)) {
                return NullValue;
            }
            return DefaultValue;
        }
 
        virtual public int GetStringLength(int record) {
            System.Diagnostics.Debug.Assert(false, "not a String or SqlString column");
            return Int32.MaxValue;
        }
 
        protected bool HasValue(int recordNo) {
            return !this.dbNullBits.Get(recordNo);
        }
 
        public virtual bool IsNull(int recordNo) {
            return this.dbNullBits.Get(recordNo);
        }
 
        // convert (may not support reference null) and store the value
        abstract public void Set(int recordNo, Object value);
 
        protected void SetNullBit(int recordNo, bool flag) {
            this.dbNullBits.Set(recordNo, flag);
        }
 
        virtual public void SetCapacity(int capacity) {
            if (null == this.dbNullBits) {
                this.dbNullBits = new BitArray(capacity);
            }
            else {
                this.dbNullBits.Length = capacity;
            }
        }
 
        abstract public object ConvertXmlToObject(string s);
        public virtual object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib) {
            return ConvertXmlToObject(xmlReader.Value);
        }
 
        abstract public string ConvertObjectToXml(object value);
        public virtual void ConvertObjectToXml(object value, XmlWriter xmlWriter, XmlRootAttribute xmlAttrib) {
            xmlWriter.WriteString(ConvertObjectToXml(value));// should it be NO OP?
        }
 
        public static DataStorage CreateStorage(DataColumn column, Type dataType, StorageType typeCode) {
            Debug.Assert(typeCode == GetStorageType(dataType), "Incorrect storage type specified");
            if ((StorageType.Empty == typeCode) && (null != dataType)) {
                if (typeof(INullable).IsAssignableFrom(dataType)) { // Udt, OracleTypes
                    return new SqlUdtStorage(column, dataType);
                }
                else {
                    return new ObjectStorage(column, dataType); // non-nullable non-primitives
                }
            }
 
            switch (typeCode) {
            case StorageType.Empty: throw ExceptionBuilder.InvalidStorageType(TypeCode.Empty);
            case StorageType.DBNull: throw ExceptionBuilder.InvalidStorageType(TypeCode.DBNull);
            case StorageType.Object: return new ObjectStorage(column, dataType);
            case StorageType.Boolean: return new BooleanStorage(column);
            case StorageType.Char: return new CharStorage(column);
            case StorageType.SByte: return new SByteStorage(column);
            case StorageType.Byte: return new ByteStorage(column);
            case StorageType.Int16: return new Int16Storage(column);
            case StorageType.UInt16: return new UInt16Storage(column);
            case StorageType.Int32: return new Int32Storage(column);
            case StorageType.UInt32: return new UInt32Storage(column);
            case StorageType.Int64: return new Int64Storage(column);
            case StorageType.UInt64: return new UInt64Storage(column);
            case StorageType.Single: return new SingleStorage(column);
            case StorageType.Double: return new DoubleStorage(column);
            case StorageType.Decimal: return new DecimalStorage(column);
            case StorageType.DateTime: return new DateTimeStorage(column);
            case StorageType.TimeSpan: return new TimeSpanStorage(column);
            case StorageType.String: return new StringStorage(column);
            case StorageType.Guid: return new ObjectStorage(column, dataType);
 
            case StorageType.ByteArray: return new ObjectStorage(column, dataType);
            case StorageType.CharArray: return new ObjectStorage(column, dataType);
            case StorageType.Type: return new ObjectStorage(column, dataType);
            case StorageType.DateTimeOffset: return new DateTimeOffsetStorage(column);
            case StorageType.BigInteger: return new BigIntegerStorage(column);
            case StorageType.Uri: return new ObjectStorage(column, dataType);
 
            case StorageType.SqlBinary: return new SqlBinaryStorage(column);
            case StorageType.SqlBoolean: return new SqlBooleanStorage(column);
            case StorageType.SqlByte: return new SqlByteStorage(column);
            case StorageType.SqlBytes: return new SqlBytesStorage(column);
            case StorageType.SqlChars: return new SqlCharsStorage(column);
            case StorageType.SqlDateTime: return new SqlDateTimeStorage(column); //???/ what to do
            case StorageType.SqlDecimal: return new SqlDecimalStorage(column);
            case StorageType.SqlDouble: return new SqlDoubleStorage(column);
            case StorageType.SqlGuid: return new SqlGuidStorage(column);
            case StorageType.SqlInt16: return new SqlInt16Storage(column);
            case StorageType.SqlInt32: return new SqlInt32Storage(column);
            case StorageType.SqlInt64: return new SqlInt64Storage(column);
            case StorageType.SqlMoney: return new SqlMoneyStorage(column);
            case StorageType.SqlSingle: return new SqlSingleStorage(column);
            case StorageType.SqlString: return new SqlStringStorage(column);
            //            case StorageType.SqlXml:         return new SqlXmlStorage(column);
 
            default:
                System.Diagnostics.Debug.Assert(false, "shouldn't be here");
                goto case StorageType.Object;
            }
        }
 
        internal static StorageType GetStorageType(Type dataType) {
            for (int i = 0; i < StorageClassType.Length; ++i) {
                if (dataType == StorageClassType[i]) {
                    return (StorageType)i;
                }
            }
            TypeCode tcode = Type.GetTypeCode(dataType);
            if (TypeCode.Object != tcode) { // enum -> Int64/Int32/Int16/Byte
                return (StorageType)tcode;
            }
            return StorageType.Empty;
        }
 
        internal static Type GetTypeStorage(StorageType storageType) {
            return StorageClassType[(int)storageType];
        }
 
        internal static bool IsTypeCustomType(Type type) {
            return IsTypeCustomType(GetStorageType(type));
        }
 
        internal static bool IsTypeCustomType(StorageType typeCode) {
            return ((StorageType.Object == typeCode) || (StorageType.Empty == typeCode) || (StorageType.CharArray == typeCode));
        }
 
        internal static bool IsSqlType(StorageType storageType) {
            return (StorageType.SqlBinary <= storageType);
        }
 
        public static bool IsSqlType(Type dataType) {
            for (int i = (int)StorageType.SqlBinary; i < StorageClassType.Length; ++i) {
                if (dataType == StorageClassType[i]) {
                    return true;
                }
            }
            return false;
        }
 
        private static bool DetermineIfValueType(StorageType typeCode, Type dataType) {
            bool result;
            switch (typeCode) {
            case StorageType.Boolean:
            case StorageType.Char:
            case StorageType.SByte:
            case StorageType.Byte:
            case StorageType.Int16:
            case StorageType.UInt16:
            case StorageType.Int32:
            case StorageType.UInt32:
            case StorageType.Int64:
            case StorageType.UInt64:
            case StorageType.Single:
            case StorageType.Double:
            case StorageType.Decimal:
            case StorageType.DateTime:
            case StorageType.DateTimeOffset:
            case StorageType.BigInteger:
            case StorageType.TimeSpan:
            case StorageType.Guid:
            case StorageType.SqlBinary:
            case StorageType.SqlBoolean:
            case StorageType.SqlByte:
            case StorageType.SqlDateTime:
            case StorageType.SqlDecimal:
            case StorageType.SqlDouble:
            case StorageType.SqlGuid:
            case StorageType.SqlInt16:
            case StorageType.SqlInt32:
            case StorageType.SqlInt64:
            case StorageType.SqlMoney:
            case StorageType.SqlSingle:
            case StorageType.SqlString:
                result = true;
                break;
 
            case StorageType.String:
            case StorageType.ByteArray:
            case StorageType.CharArray:
            case StorageType.Type:
            case StorageType.Uri:
            case StorageType.SqlBytes:
            case StorageType.SqlChars:
                result = false;
                break;
 
            default:
                result = dataType.IsValueType;
                break;
            }
            Debug.Assert(result == dataType.IsValueType, "typeCode mismatches dataType");
            return result;
        }
 
        internal static void ImplementsInterfaces(
                                    StorageType typeCode,
                                    Type dataType,
                                    out bool sqlType,
                                    out bool nullable,
                                    out bool xmlSerializable,
                                    out bool changeTracking,
                                    out bool revertibleChangeTracking)
        {
            Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType");
            if (IsSqlType(typeCode)) {
                sqlType = true;
                nullable = true;
                changeTracking = false;
                revertibleChangeTracking = false;
                xmlSerializable = true;
            }
            else if (StorageType.Empty != typeCode) {
                sqlType = false;
                nullable = false;
                changeTracking = false;
                revertibleChangeTracking = false;
                xmlSerializable = false;
            }
            else {
                // Non-standard type - look it up in the dictionary or add it if not found
                Tuple<bool, bool, bool, bool> interfaces = _typeImplementsInterface.GetOrAdd(dataType, _inspectTypeForInterfaces);
                sqlType = false;
                nullable = interfaces.Item1;
                changeTracking = interfaces.Item2;
                revertibleChangeTracking = interfaces.Item3;
                xmlSerializable = interfaces.Item4;
            }
            Debug.Assert(nullable == typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType), "INullable");
            Debug.Assert(changeTracking == typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType), "IChangeTracking");
            Debug.Assert(revertibleChangeTracking == typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType), "IRevertibleChangeTracking");
            Debug.Assert(xmlSerializable == typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType), "IXmlSerializable");
        }
 
        private static Tuple<bool, bool, bool, bool> InspectTypeForInterfaces(Type dataType) {
            Debug.Assert(dataType != null, "Type should not be null");
 
            return new Tuple<bool,bool,bool,bool>(
                typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType),
                typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType),
                typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType),
                typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType));
        }
 
        internal static bool ImplementsINullableValue(StorageType typeCode, Type dataType) {
            Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType");
            return ((StorageType.Empty == typeCode) && dataType.IsGenericType && (dataType.GetGenericTypeDefinition() == typeof(System.Nullable<>)));
        }
 
        public static bool IsObjectNull(object value) {
            return ((null == value) || (DBNull.Value == value) || IsObjectSqlNull(value));
        }
 
        public static bool IsObjectSqlNull(object value) {
            INullable inullable = (value as INullable);
            return ((null != inullable) && inullable.IsNull);
        }
 
        internal object GetEmptyStorageInternal(int recordCount) {
            return GetEmptyStorage(recordCount);
        }
 
        internal void CopyValueInternal(int record, object store, BitArray nullbits, int storeIndex) {
            CopyValue(record, store, nullbits, storeIndex);
        }
 
        internal void SetStorageInternal(object store, BitArray nullbits) {
            SetStorage(store, nullbits);
        }
 
        abstract protected Object GetEmptyStorage(int recordCount);
        abstract protected void CopyValue(int record, object store, BitArray nullbits, int storeIndex);
        abstract protected void SetStorage(object store, BitArray nullbits);
        protected void SetNullStorage(BitArray nullbits) {
            dbNullBits = nullbits;
        }
 
        /// <summary>wrapper around Type.GetType</summary>
        /// <param name="value">assembly qualified type name or one of the special known types</param>
        /// <returns>Type or null if not found</returns>
        /// <exception cref="InvalidOperationException">when type implements IDynamicMetaObjectProvider and not IXmlSerializable</exception>
        /// <remarks>
        /// Types like "System.Guid" will load regardless of AssemblyQualifiedName because they are special
        /// Types like "System.Data.SqlTypes.SqlString" will load because they are in the same assembly as this code
        /// Types like "System.Numerics.BigInteger" won't load because they are not special and not same assembly as this code
        /// </remarks>
        internal static Type GetType(string value) {
            Type dataType = Type.GetType(value); // throwOnError=false, ignoreCase=fase
            if (null == dataType) {
                if ("System.Numerics.BigInteger" == value) {
                    dataType = typeof(System.Numerics.BigInteger);
                }
            }
 
            // Dev10 671061: prevent reading type from schema which implements IDynamicMetaObjectProvider and not IXmlSerializable
            // the check here prevents the type from being loaded in schema or as instance data (when DataType is object)
            ObjectStorage.VerifyIDynamicMetaObjectProvider(dataType);
            return dataType;
        }
 
        /// <summary>wrapper around Type.AssemblyQualifiedName</summary>
        /// <param name="type"></param>
        /// <returns>qualified name when writing in xml</returns>
        /// <exception cref="InvalidOperationException">when type implements IDynamicMetaObjectProvider and not IXmlSerializable</exception>
        internal static string GetQualifiedName(Type type)
        {
            Debug.Assert(null != type, "null type");
            ObjectStorage.VerifyIDynamicMetaObjectProvider(type);
            return type.AssemblyQualifiedName;
        }
    }
}