536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
534 lines
22 KiB
C#
534 lines
22 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|