1 //------------------------------------------------------------------------------ 2 // <copyright file="DataStorage.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</owner> 6 // <owner current="true" primary="false">Microsoft</owner> 7 //------------------------------------------------------------------------------ 8 9 namespace System.Data.Common { 10 using System; 11 using System.Collections; 12 using System.Collections.Concurrent; 13 using System.Collections.Generic; 14 using System.Data.SqlTypes; 15 using System.Diagnostics; 16 using System.Xml; 17 using System.Xml.Serialization; 18 19 internal enum StorageType { 20 Empty = TypeCode.Empty, // 0 21 Object = TypeCode.Object, 22 DBNull = TypeCode.DBNull, 23 Boolean = TypeCode.Boolean, 24 Char = TypeCode.Char, 25 SByte = TypeCode.SByte, 26 Byte = TypeCode.Byte, 27 Int16 = TypeCode.Int16, 28 UInt16 = TypeCode.UInt16, 29 Int32 = TypeCode.Int32, 30 UInt32 = TypeCode.UInt32, 31 Int64 = TypeCode.Int64, 32 UInt64 = TypeCode.UInt64, 33 Single = TypeCode.Single, 34 Double = TypeCode.Double, 35 Decimal = TypeCode.Decimal, // 15 36 DateTime = TypeCode.DateTime, // 16 37 TimeSpan = 17, 38 String = TypeCode.String, // 18 39 Guid = 19, 40 41 ByteArray = 20, 42 CharArray = 21, 43 Type = 22, 44 DateTimeOffset = 23, 45 BigInteger = 24, 46 Uri = 25, 47 48 SqlBinary, // SqlTypes should remain at the end for IsSqlType checking 49 SqlBoolean, 50 SqlByte, 51 SqlBytes, 52 SqlChars, 53 SqlDateTime, 54 SqlDecimal, 55 SqlDouble, 56 SqlGuid, 57 SqlInt16, 58 SqlInt32, 59 SqlInt64, 60 SqlMoney, 61 SqlSingle, 62 SqlString, 63 // SqlXml, 64 }; 65 66 abstract internal class DataStorage { 67 68 // for Whidbey 40426, searching down the Type[] is about 20% faster than using a Dictionary 69 // must keep in same order as enum StorageType 70 private static readonly Type[] StorageClassType = new Type[] { 71 null, 72 typeof(Object), 73 typeof(DBNull), 74 typeof(Boolean), 75 typeof(Char), 76 typeof(SByte), 77 typeof(Byte), 78 typeof(Int16), 79 typeof(UInt16), 80 typeof(Int32), 81 typeof(UInt32), 82 typeof(Int64), 83 typeof(UInt64), 84 typeof(Single), 85 typeof(Double), 86 typeof(Decimal), 87 typeof(DateTime), 88 typeof(TimeSpan), 89 typeof(String), 90 typeof(Guid), 91 92 typeof(byte[]), 93 typeof(char[]), 94 typeof(Type), 95 typeof(DateTimeOffset), 96 typeof(System.Numerics.BigInteger), 97 typeof(Uri), 98 99 typeof(SqlBinary), 100 typeof(SqlBoolean), 101 typeof(SqlByte), 102 typeof(SqlBytes), 103 typeof(SqlChars), 104 typeof(SqlDateTime), 105 typeof(SqlDecimal), 106 typeof(SqlDouble), 107 typeof(SqlGuid), 108 typeof(SqlInt16), 109 typeof(SqlInt32), 110 typeof(SqlInt64), 111 typeof(SqlMoney), 112 typeof(SqlSingle), 113 typeof(SqlString), 114 // typeof(SqlXml), 115 }; 116 117 internal readonly DataColumn Column; 118 internal readonly DataTable Table; 119 internal readonly Type DataType; 120 internal readonly StorageType StorageTypeCode; 121 private System.Collections.BitArray dbNullBits; 122 123 private readonly object DefaultValue; 124 internal readonly object NullValue; 125 126 internal readonly bool IsCloneable; 127 internal readonly bool IsCustomDefinedType; 128 internal readonly bool IsStringType; 129 internal readonly bool IsValueType; 130 131 private readonly static Func<Type, Tuple<bool, bool, bool, bool>> _inspectTypeForInterfaces = InspectTypeForInterfaces; 132 private readonly static ConcurrentDictionary<Type, Tuple<bool, bool, bool, bool>> _typeImplementsInterface = new ConcurrentDictionary<Type, Tuple<bool, bool, bool, bool>>(); 133 DataStorage(DataColumn column, Type type, object defaultValue, StorageType storageType)134 protected DataStorage(DataColumn column, Type type, object defaultValue, StorageType storageType) 135 : this(column, type, defaultValue, DBNull.Value, false, storageType) { 136 } 137 DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, StorageType storageType)138 protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, StorageType storageType) 139 : this(column, type, defaultValue, nullValue, false, storageType) { 140 } 141 DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, bool isICloneable, StorageType storageType)142 protected DataStorage(DataColumn column, Type type, object defaultValue, object nullValue, bool isICloneable, StorageType storageType) { 143 Debug.Assert(storageType == GetStorageType(type), "Incorrect storage type specified"); 144 Column = column; 145 Table = column.Table; 146 DataType = type; 147 StorageTypeCode = storageType; 148 DefaultValue = defaultValue; 149 NullValue = nullValue; 150 IsCloneable = isICloneable; 151 IsCustomDefinedType = IsTypeCustomType(StorageTypeCode); 152 IsStringType = ((StorageType.String == StorageTypeCode) || (StorageType.SqlString == StorageTypeCode)); 153 IsValueType = DetermineIfValueType(StorageTypeCode, type); 154 } 155 156 internal DataSetDateTime DateTimeMode { 157 get { 158 return Column.DateTimeMode; 159 } 160 } 161 162 internal IFormatProvider FormatProvider { 163 get { 164 return Table.FormatProvider; 165 } 166 } 167 Aggregate(int[] recordNos, AggregateType kind)168 public virtual Object Aggregate(int[] recordNos, AggregateType kind) { 169 if (AggregateType.Count == kind) { 170 return this.AggregateCount(recordNos); 171 } 172 return null; 173 } 174 AggregateCount(int[] recordNos)175 public object AggregateCount(int[] recordNos) { 176 int count = 0; 177 for (int i = 0; i < recordNos.Length; i++) { 178 if (!this.dbNullBits.Get(recordNos[i])) 179 count++; 180 } 181 return count; 182 } 183 CompareBits(int recordNo1, int recordNo2)184 protected int CompareBits(int recordNo1, int recordNo2) { 185 bool recordNo1Null = this.dbNullBits.Get(recordNo1); 186 bool recordNo2Null = this.dbNullBits.Get(recordNo2); 187 if (recordNo1Null ^ recordNo2Null) { 188 if (recordNo1Null) 189 return -1; 190 else 191 return 1; 192 } 193 194 return 0; 195 } 196 Compare(int recordNo1, int recordNo2)197 public abstract int Compare(int recordNo1, int recordNo2); 198 199 // only does comparision, expect value to be of the correct type CompareValueTo(int recordNo1, object value)200 public abstract int CompareValueTo(int recordNo1, object value); 201 202 // only does conversion with support for reference null ConvertValue(object value)203 public virtual object ConvertValue(object value) { 204 return value; 205 } 206 CopyBits(int srcRecordNo, int dstRecordNo)207 protected void CopyBits(int srcRecordNo, int dstRecordNo) { 208 this.dbNullBits.Set(dstRecordNo, this.dbNullBits.Get(srcRecordNo)); 209 } 210 Copy(int recordNo1, int recordNo2)211 abstract public void Copy(int recordNo1, int recordNo2); 212 Get(int recordNo)213 abstract public Object Get(int recordNo); 214 GetBits(int recordNo)215 protected Object GetBits(int recordNo) { 216 if (this.dbNullBits.Get(recordNo)) { 217 return NullValue; 218 } 219 return DefaultValue; 220 } 221 GetStringLength(int record)222 virtual public int GetStringLength(int record) { 223 System.Diagnostics.Debug.Assert(false, "not a String or SqlString column"); 224 return Int32.MaxValue; 225 } 226 HasValue(int recordNo)227 protected bool HasValue(int recordNo) { 228 return !this.dbNullBits.Get(recordNo); 229 } 230 IsNull(int recordNo)231 public virtual bool IsNull(int recordNo) { 232 return this.dbNullBits.Get(recordNo); 233 } 234 235 // convert (may not support reference null) and store the value Set(int recordNo, Object value)236 abstract public void Set(int recordNo, Object value); 237 SetNullBit(int recordNo, bool flag)238 protected void SetNullBit(int recordNo, bool flag) { 239 this.dbNullBits.Set(recordNo, flag); 240 } 241 SetCapacity(int capacity)242 virtual public void SetCapacity(int capacity) { 243 if (null == this.dbNullBits) { 244 this.dbNullBits = new BitArray(capacity); 245 } 246 else { 247 this.dbNullBits.Length = capacity; 248 } 249 } 250 ConvertXmlToObject(string s)251 abstract public object ConvertXmlToObject(string s); ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib)252 public virtual object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib) { 253 return ConvertXmlToObject(xmlReader.Value); 254 } 255 ConvertObjectToXml(object value)256 abstract public string ConvertObjectToXml(object value); ConvertObjectToXml(object value, XmlWriter xmlWriter, XmlRootAttribute xmlAttrib)257 public virtual void ConvertObjectToXml(object value, XmlWriter xmlWriter, XmlRootAttribute xmlAttrib) { 258 xmlWriter.WriteString(ConvertObjectToXml(value));// should it be NO OP? 259 } 260 CreateStorage(DataColumn column, Type dataType, StorageType typeCode)261 public static DataStorage CreateStorage(DataColumn column, Type dataType, StorageType typeCode) { 262 Debug.Assert(typeCode == GetStorageType(dataType), "Incorrect storage type specified"); 263 if ((StorageType.Empty == typeCode) && (null != dataType)) { 264 if (typeof(INullable).IsAssignableFrom(dataType)) { // Udt, OracleTypes 265 return new SqlUdtStorage(column, dataType); 266 } 267 else { 268 return new ObjectStorage(column, dataType); // non-nullable non-primitives 269 } 270 } 271 272 switch (typeCode) { 273 case StorageType.Empty: throw ExceptionBuilder.InvalidStorageType(TypeCode.Empty); 274 case StorageType.DBNull: throw ExceptionBuilder.InvalidStorageType(TypeCode.DBNull); 275 case StorageType.Object: return new ObjectStorage(column, dataType); 276 case StorageType.Boolean: return new BooleanStorage(column); 277 case StorageType.Char: return new CharStorage(column); 278 case StorageType.SByte: return new SByteStorage(column); 279 case StorageType.Byte: return new ByteStorage(column); 280 case StorageType.Int16: return new Int16Storage(column); 281 case StorageType.UInt16: return new UInt16Storage(column); 282 case StorageType.Int32: return new Int32Storage(column); 283 case StorageType.UInt32: return new UInt32Storage(column); 284 case StorageType.Int64: return new Int64Storage(column); 285 case StorageType.UInt64: return new UInt64Storage(column); 286 case StorageType.Single: return new SingleStorage(column); 287 case StorageType.Double: return new DoubleStorage(column); 288 case StorageType.Decimal: return new DecimalStorage(column); 289 case StorageType.DateTime: return new DateTimeStorage(column); 290 case StorageType.TimeSpan: return new TimeSpanStorage(column); 291 case StorageType.String: return new StringStorage(column); 292 case StorageType.Guid: return new ObjectStorage(column, dataType); 293 294 case StorageType.ByteArray: return new ObjectStorage(column, dataType); 295 case StorageType.CharArray: return new ObjectStorage(column, dataType); 296 case StorageType.Type: return new ObjectStorage(column, dataType); 297 case StorageType.DateTimeOffset: return new DateTimeOffsetStorage(column); 298 case StorageType.BigInteger: return new BigIntegerStorage(column); 299 case StorageType.Uri: return new ObjectStorage(column, dataType); 300 301 case StorageType.SqlBinary: return new SqlBinaryStorage(column); 302 case StorageType.SqlBoolean: return new SqlBooleanStorage(column); 303 case StorageType.SqlByte: return new SqlByteStorage(column); 304 case StorageType.SqlBytes: return new SqlBytesStorage(column); 305 case StorageType.SqlChars: return new SqlCharsStorage(column); 306 case StorageType.SqlDateTime: return new SqlDateTimeStorage(column); //???/ what to do 307 case StorageType.SqlDecimal: return new SqlDecimalStorage(column); 308 case StorageType.SqlDouble: return new SqlDoubleStorage(column); 309 case StorageType.SqlGuid: return new SqlGuidStorage(column); 310 case StorageType.SqlInt16: return new SqlInt16Storage(column); 311 case StorageType.SqlInt32: return new SqlInt32Storage(column); 312 case StorageType.SqlInt64: return new SqlInt64Storage(column); 313 case StorageType.SqlMoney: return new SqlMoneyStorage(column); 314 case StorageType.SqlSingle: return new SqlSingleStorage(column); 315 case StorageType.SqlString: return new SqlStringStorage(column); 316 // case StorageType.SqlXml: return new SqlXmlStorage(column); 317 318 default: 319 System.Diagnostics.Debug.Assert(false, "shouldn't be here"); 320 goto case StorageType.Object; 321 } 322 } 323 GetStorageType(Type dataType)324 internal static StorageType GetStorageType(Type dataType) { 325 for (int i = 0; i < StorageClassType.Length; ++i) { 326 if (dataType == StorageClassType[i]) { 327 return (StorageType)i; 328 } 329 } 330 TypeCode tcode = Type.GetTypeCode(dataType); 331 if (TypeCode.Object != tcode) { // enum -> Int64/Int32/Int16/Byte 332 return (StorageType)tcode; 333 } 334 return StorageType.Empty; 335 } 336 GetTypeStorage(StorageType storageType)337 internal static Type GetTypeStorage(StorageType storageType) { 338 return StorageClassType[(int)storageType]; 339 } 340 IsTypeCustomType(Type type)341 internal static bool IsTypeCustomType(Type type) { 342 return IsTypeCustomType(GetStorageType(type)); 343 } 344 IsTypeCustomType(StorageType typeCode)345 internal static bool IsTypeCustomType(StorageType typeCode) { 346 return ((StorageType.Object == typeCode) || (StorageType.Empty == typeCode) || (StorageType.CharArray == typeCode)); 347 } 348 IsSqlType(StorageType storageType)349 internal static bool IsSqlType(StorageType storageType) { 350 return (StorageType.SqlBinary <= storageType); 351 } 352 IsSqlType(Type dataType)353 public static bool IsSqlType(Type dataType) { 354 for (int i = (int)StorageType.SqlBinary; i < StorageClassType.Length; ++i) { 355 if (dataType == StorageClassType[i]) { 356 return true; 357 } 358 } 359 return false; 360 } 361 DetermineIfValueType(StorageType typeCode, Type dataType)362 private static bool DetermineIfValueType(StorageType typeCode, Type dataType) { 363 bool result; 364 switch (typeCode) { 365 case StorageType.Boolean: 366 case StorageType.Char: 367 case StorageType.SByte: 368 case StorageType.Byte: 369 case StorageType.Int16: 370 case StorageType.UInt16: 371 case StorageType.Int32: 372 case StorageType.UInt32: 373 case StorageType.Int64: 374 case StorageType.UInt64: 375 case StorageType.Single: 376 case StorageType.Double: 377 case StorageType.Decimal: 378 case StorageType.DateTime: 379 case StorageType.DateTimeOffset: 380 case StorageType.BigInteger: 381 case StorageType.TimeSpan: 382 case StorageType.Guid: 383 case StorageType.SqlBinary: 384 case StorageType.SqlBoolean: 385 case StorageType.SqlByte: 386 case StorageType.SqlDateTime: 387 case StorageType.SqlDecimal: 388 case StorageType.SqlDouble: 389 case StorageType.SqlGuid: 390 case StorageType.SqlInt16: 391 case StorageType.SqlInt32: 392 case StorageType.SqlInt64: 393 case StorageType.SqlMoney: 394 case StorageType.SqlSingle: 395 case StorageType.SqlString: 396 result = true; 397 break; 398 399 case StorageType.String: 400 case StorageType.ByteArray: 401 case StorageType.CharArray: 402 case StorageType.Type: 403 case StorageType.Uri: 404 case StorageType.SqlBytes: 405 case StorageType.SqlChars: 406 result = false; 407 break; 408 409 default: 410 result = dataType.IsValueType; 411 break; 412 } 413 Debug.Assert(result == dataType.IsValueType, "typeCode mismatches dataType"); 414 return result; 415 } 416 ImplementsInterfaces( StorageType typeCode, Type dataType, out bool sqlType, out bool nullable, out bool xmlSerializable, out bool changeTracking, out bool revertibleChangeTracking)417 internal static void ImplementsInterfaces( 418 StorageType typeCode, 419 Type dataType, 420 out bool sqlType, 421 out bool nullable, 422 out bool xmlSerializable, 423 out bool changeTracking, 424 out bool revertibleChangeTracking) 425 { 426 Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType"); 427 if (IsSqlType(typeCode)) { 428 sqlType = true; 429 nullable = true; 430 changeTracking = false; 431 revertibleChangeTracking = false; 432 xmlSerializable = true; 433 } 434 else if (StorageType.Empty != typeCode) { 435 sqlType = false; 436 nullable = false; 437 changeTracking = false; 438 revertibleChangeTracking = false; 439 xmlSerializable = false; 440 } 441 else { 442 // Non-standard type - look it up in the dictionary or add it if not found 443 Tuple<bool, bool, bool, bool> interfaces = _typeImplementsInterface.GetOrAdd(dataType, _inspectTypeForInterfaces); 444 sqlType = false; 445 nullable = interfaces.Item1; 446 changeTracking = interfaces.Item2; 447 revertibleChangeTracking = interfaces.Item3; 448 xmlSerializable = interfaces.Item4; 449 } 450 Debug.Assert(nullable == typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType), "INullable"); 451 Debug.Assert(changeTracking == typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType), "IChangeTracking"); 452 Debug.Assert(revertibleChangeTracking == typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType), "IRevertibleChangeTracking"); 453 Debug.Assert(xmlSerializable == typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType), "IXmlSerializable"); 454 } 455 InspectTypeForInterfaces(Type dataType)456 private static Tuple<bool, bool, bool, bool> InspectTypeForInterfaces(Type dataType) { 457 Debug.Assert(dataType != null, "Type should not be null"); 458 459 return new Tuple<bool,bool,bool,bool>( 460 typeof(System.Data.SqlTypes.INullable).IsAssignableFrom(dataType), 461 typeof(System.ComponentModel.IChangeTracking).IsAssignableFrom(dataType), 462 typeof(System.ComponentModel.IRevertibleChangeTracking).IsAssignableFrom(dataType), 463 typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(dataType)); 464 } 465 ImplementsINullableValue(StorageType typeCode, Type dataType)466 internal static bool ImplementsINullableValue(StorageType typeCode, Type dataType) { 467 Debug.Assert(typeCode == GetStorageType(dataType), "typeCode mismatches dataType"); 468 return ((StorageType.Empty == typeCode) && dataType.IsGenericType && (dataType.GetGenericTypeDefinition() == typeof(System.Nullable<>))); 469 } 470 IsObjectNull(object value)471 public static bool IsObjectNull(object value) { 472 return ((null == value) || (DBNull.Value == value) || IsObjectSqlNull(value)); 473 } 474 IsObjectSqlNull(object value)475 public static bool IsObjectSqlNull(object value) { 476 INullable inullable = (value as INullable); 477 return ((null != inullable) && inullable.IsNull); 478 } 479 GetEmptyStorageInternal(int recordCount)480 internal object GetEmptyStorageInternal(int recordCount) { 481 return GetEmptyStorage(recordCount); 482 } 483 CopyValueInternal(int record, object store, BitArray nullbits, int storeIndex)484 internal void CopyValueInternal(int record, object store, BitArray nullbits, int storeIndex) { 485 CopyValue(record, store, nullbits, storeIndex); 486 } 487 SetStorageInternal(object store, BitArray nullbits)488 internal void SetStorageInternal(object store, BitArray nullbits) { 489 SetStorage(store, nullbits); 490 } 491 GetEmptyStorage(int recordCount)492 abstract protected Object GetEmptyStorage(int recordCount); CopyValue(int record, object store, BitArray nullbits, int storeIndex)493 abstract protected void CopyValue(int record, object store, BitArray nullbits, int storeIndex); SetStorage(object store, BitArray nullbits)494 abstract protected void SetStorage(object store, BitArray nullbits); SetNullStorage(BitArray nullbits)495 protected void SetNullStorage(BitArray nullbits) { 496 dbNullBits = nullbits; 497 } 498 499 /// <summary>wrapper around Type.GetType</summary> 500 /// <param name="value">assembly qualified type name or one of the special known types</param> 501 /// <returns>Type or null if not found</returns> 502 /// <exception cref="InvalidOperationException">when type implements IDynamicMetaObjectProvider and not IXmlSerializable</exception> 503 /// <remarks> 504 /// Types like "System.Guid" will load regardless of AssemblyQualifiedName because they are special 505 /// Types like "System.Data.SqlTypes.SqlString" will load because they are in the same assembly as this code 506 /// Types like "System.Numerics.BigInteger" won't load because they are not special and not same assembly as this code 507 /// </remarks> GetType(string value)508 internal static Type GetType(string value) { 509 Type dataType = Type.GetType(value); // throwOnError=false, ignoreCase=fase 510 if (null == dataType) { 511 if ("System.Numerics.BigInteger" == value) { 512 dataType = typeof(System.Numerics.BigInteger); 513 } 514 } 515 516 // Dev10 671061: prevent reading type from schema which implements IDynamicMetaObjectProvider and not IXmlSerializable 517 // the check here prevents the type from being loaded in schema or as instance data (when DataType is object) 518 ObjectStorage.VerifyIDynamicMetaObjectProvider(dataType); 519 return dataType; 520 } 521 522 /// <summary>wrapper around Type.AssemblyQualifiedName</summary> 523 /// <param name="type"></param> 524 /// <returns>qualified name when writing in xml</returns> 525 /// <exception cref="InvalidOperationException">when type implements IDynamicMetaObjectProvider and not IXmlSerializable</exception> GetQualifiedName(Type type)526 internal static string GetQualifiedName(Type type) 527 { 528 Debug.Assert(null != type, "null type"); 529 ObjectStorage.VerifyIDynamicMetaObjectProvider(type); 530 return type.AssemblyQualifiedName; 531 } 532 } 533 } 534