1 //------------------------------------------------------------------------------
2 // <copyright file="MetaDataUtilsSmi.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
9 namespace Microsoft.SqlServer.Server {
12 using System.Collections;
13 using System.Collections.Generic;
15 using System.Data.Sql;
16 using System.Data.Common;
17 using System.Data.SqlClient;
18 using System.Data.SqlTypes;
19 using System.Diagnostics;
22 // Utilities for manipulating smi-related metadata.
24 // THIS CLASS IS BUILT ON TOP OF THE SMI INTERFACE -- SMI SHOULD NOT DEPEND ON IT!
26 // These are all based off of knowing the clr type of the value
27 // as an ExtendedClrTypeCode enum for rapid access (lookup in static array is best, if possible).
28 internal class MetaDataUtilsSmi {
30 internal const SqlDbType InvalidSqlDbType = (SqlDbType) (-1);
31 internal const long InvalidMaxLength = -2;
33 // Standard type inference map to get SqlDbType when all you know is the value's type (typecode)
34 // This map's index is off by one (add one to typecode locate correct entry) in order
35 // support ExtendedSqlDbType.Invalid
36 // ONLY ACCESS THIS ARRAY FROM InferSqlDbTypeFromTypeCode!!!
37 static readonly SqlDbType[] __extendedTypeCodeToSqlDbTypeMap = {
38 InvalidSqlDbType, // Invalid extended type code
39 SqlDbType.Bit, // System.Boolean
40 SqlDbType.TinyInt, // System.Byte
41 SqlDbType.NVarChar, // System.Char
42 SqlDbType.DateTime, // System.DateTime
43 InvalidSqlDbType, // System.DBNull doesn't have an inferable SqlDbType
44 SqlDbType.Decimal, // System.Decimal
45 SqlDbType.Float, // System.Double
46 InvalidSqlDbType, // null reference doesn't have an inferable SqlDbType
47 SqlDbType.SmallInt, // System.Int16
48 SqlDbType.Int, // System.Int32
49 SqlDbType.BigInt, // System.Int64
50 InvalidSqlDbType, // System.SByte doesn't have an inferable SqlDbType
51 SqlDbType.Real, // System.Single
52 SqlDbType.NVarChar, // System.String
53 InvalidSqlDbType, // System.UInt16 doesn't have an inferable SqlDbType
54 InvalidSqlDbType, // System.UInt32 doesn't have an inferable SqlDbType
55 InvalidSqlDbType, // System.UInt64 doesn't have an inferable SqlDbType
56 InvalidSqlDbType, // System.Object doesn't have an inferable SqlDbType
57 SqlDbType.VarBinary, // System.ByteArray
58 SqlDbType.NVarChar, // System.CharArray
59 SqlDbType.UniqueIdentifier, // System.Guid
60 SqlDbType.VarBinary, // System.Data.SqlTypes.SqlBinary
61 SqlDbType.Bit, // System.Data.SqlTypes.SqlBoolean
62 SqlDbType.TinyInt, // System.Data.SqlTypes.SqlByte
63 SqlDbType.DateTime, // System.Data.SqlTypes.SqlDateTime
64 SqlDbType.Float, // System.Data.SqlTypes.SqlDouble
65 SqlDbType.UniqueIdentifier, // System.Data.SqlTypes.SqlGuid
66 SqlDbType.SmallInt, // System.Data.SqlTypes.SqlInt16
67 SqlDbType.Int, // System.Data.SqlTypes.SqlInt32
68 SqlDbType.BigInt, // System.Data.SqlTypes.SqlInt64
69 SqlDbType.Money, // System.Data.SqlTypes.SqlMoney
70 SqlDbType.Decimal, // System.Data.SqlTypes.SqlDecimal
71 SqlDbType.Real, // System.Data.SqlTypes.SqlSingle
72 SqlDbType.NVarChar, // System.Data.SqlTypes.SqlString
73 SqlDbType.NVarChar, // System.Data.SqlTypes.SqlChars
74 SqlDbType.VarBinary, // System.Data.SqlTypes.SqlBytes
75 SqlDbType.Xml, // System.Data.SqlTypes.SqlXml
76 SqlDbType.Structured, // System.Data.DataTable
77 SqlDbType.Structured, // System.Collections.IEnumerable, used for TVPs it must return IDataRecord
78 SqlDbType.Structured, // System.Collections.Generic.IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>
79 SqlDbType.Time, // System.TimeSpan
80 SqlDbType.DateTimeOffset, // System.DateTimeOffset
83 // Hash table to map from clr type object to ExtendedClrTypeCodeMap enum
84 // ONLY ACCESS THIS HASH TABLE FROM DetermineExtendedTypeCode METHOD!!! (and class ctor for setup)
85 static readonly Hashtable __typeToExtendedTypeCodeMap;
89 static MetaDataUtilsSmi() {
90 // set up type mapping hash table
91 // keep this initialization list in the same order as ExtendedClrTypeCode for ease in validating!
92 Hashtable ht = new Hashtable(42);
93 ht.Add( typeof( System.Boolean ), ExtendedClrTypeCode.Boolean );
94 ht.Add( typeof( System.Byte ), ExtendedClrTypeCode.Byte );
95 ht.Add( typeof( System.Char ), ExtendedClrTypeCode.Char );
96 ht.Add( typeof( System.DateTime ), ExtendedClrTypeCode.DateTime );
97 ht.Add( typeof( System.DBNull ), ExtendedClrTypeCode.DBNull );
98 ht.Add( typeof( System.Decimal ), ExtendedClrTypeCode.Decimal );
99 ht.Add( typeof( System.Double ), ExtendedClrTypeCode.Double );
100 // lookup code will have to special-case null-ref anyway, so don't bother adding ExtendedTypeCode.Empty to the table
101 ht.Add( typeof( System.Int16 ), ExtendedClrTypeCode.Int16 );
102 ht.Add( typeof( System.Int32 ), ExtendedClrTypeCode.Int32 );
103 ht.Add( typeof( System.Int64 ), ExtendedClrTypeCode.Int64 );
104 ht.Add( typeof( System.SByte ), ExtendedClrTypeCode.SByte );
105 ht.Add( typeof( System.Single ), ExtendedClrTypeCode.Single );
106 ht.Add( typeof( System.String ), ExtendedClrTypeCode.String );
107 ht.Add( typeof( System.UInt16 ), ExtendedClrTypeCode.UInt16 );
108 ht.Add( typeof( System.UInt32 ), ExtendedClrTypeCode.UInt32 );
109 ht.Add( typeof( System.UInt64 ), ExtendedClrTypeCode.UInt64 );
110 ht.Add( typeof( System.Object ), ExtendedClrTypeCode.Object );
111 ht.Add( typeof( System.Byte[] ), ExtendedClrTypeCode.ByteArray );
112 ht.Add( typeof( System.Char[] ), ExtendedClrTypeCode.CharArray );
113 ht.Add( typeof( System.Guid ), ExtendedClrTypeCode.Guid );
114 ht.Add( typeof( SqlBinary ), ExtendedClrTypeCode.SqlBinary );
115 ht.Add( typeof( SqlBoolean ), ExtendedClrTypeCode.SqlBoolean );
116 ht.Add( typeof( SqlByte ), ExtendedClrTypeCode.SqlByte );
117 ht.Add( typeof( SqlDateTime ), ExtendedClrTypeCode.SqlDateTime );
118 ht.Add( typeof( SqlDouble ), ExtendedClrTypeCode.SqlDouble );
119 ht.Add( typeof( SqlGuid ), ExtendedClrTypeCode.SqlGuid );
120 ht.Add( typeof( SqlInt16 ), ExtendedClrTypeCode.SqlInt16 );
121 ht.Add( typeof( SqlInt32 ), ExtendedClrTypeCode.SqlInt32 );
122 ht.Add( typeof( SqlInt64 ), ExtendedClrTypeCode.SqlInt64 );
123 ht.Add( typeof( SqlMoney ), ExtendedClrTypeCode.SqlMoney );
124 ht.Add( typeof( SqlDecimal ), ExtendedClrTypeCode.SqlDecimal );
125 ht.Add( typeof( SqlSingle ), ExtendedClrTypeCode.SqlSingle );
126 ht.Add( typeof( SqlString ), ExtendedClrTypeCode.SqlString );
127 ht.Add( typeof( SqlChars ), ExtendedClrTypeCode.SqlChars );
128 ht.Add( typeof( SqlBytes ), ExtendedClrTypeCode.SqlBytes );
129 ht.Add( typeof( SqlXml ), ExtendedClrTypeCode.SqlXml );
130 ht.Add( typeof( DataTable ), ExtendedClrTypeCode.DataTable );
131 ht.Add( typeof( DbDataReader ), ExtendedClrTypeCode.DbDataReader );
132 ht.Add( typeof( IEnumerable<SqlDataRecord> ), ExtendedClrTypeCode.IEnumerableOfSqlDataRecord );
133 ht.Add( typeof( System.TimeSpan ), ExtendedClrTypeCode.TimeSpan );
134 ht.Add( typeof( System.DateTimeOffset ), ExtendedClrTypeCode.DateTimeOffset );
135 __typeToExtendedTypeCodeMap = ht;
139 internal static bool IsCharOrXmlType(SqlDbType type) {
140 return IsUnicodeType(type) ||
142 type == SqlDbType.Xml;
145 internal static bool IsUnicodeType(SqlDbType type) {
146 return type == SqlDbType.NChar ||
147 type == SqlDbType.NVarChar ||
148 type == SqlDbType.NText;
151 internal static bool IsAnsiType(SqlDbType type) {
152 return type == SqlDbType.Char ||
153 type == SqlDbType.VarChar ||
154 type == SqlDbType.Text;
157 internal static bool IsBinaryType(SqlDbType type) {
158 return type == SqlDbType.Binary ||
159 type == SqlDbType.VarBinary ||
160 type == SqlDbType.Image;
163 // Does this type use PLP format values?
164 internal static bool IsPlpFormat(SmiMetaData metaData) {
165 return metaData.MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator ||
166 metaData.SqlDbType == SqlDbType.Image ||
167 metaData.SqlDbType == SqlDbType.NText ||
168 metaData.SqlDbType == SqlDbType.Text ||
169 metaData.SqlDbType == SqlDbType.Udt;
174 // If we know we're only going to use this object to assign to a specific SqlDbType back end object,
175 // we can save some processing time by only checking for the few valid types that can be assigned to the dbType.
176 // This assumes a switch statement over SqlDbType is faster than getting the ClrTypeCode and iterating over a
177 // series of if statements, or using a hash table.
178 // NOTE: the form of these checks is taking advantage of a feature of the JIT compiler that is supposed to
179 // optimize checks of the form '(xxx.GetType() == typeof( YYY ))'. The JIT team claimed at one point that
180 // this doesn't even instantiate a Type instance, thus was the fastest method for individual comparisions.
181 // Given that there's a known SqlDbType, thus a minimal number of comparisions, it's likely this is faster
182 // than the other approaches considered (both GetType().GetTypeCode() switch and hash table using Type keys
183 // must instantiate a Type object. The typecode switch also degenerates into a large if-then-else for
184 // all but the primitive clr types.
185 internal static ExtendedClrTypeCode DetermineExtendedTypeCodeForUseWithSqlDbType(
191 ExtendedClrTypeCode extendedCode = ExtendedClrTypeCode.Invalid;
193 // fast-track null, which is valid for all types
194 if ( null == value ) {
195 extendedCode = ExtendedClrTypeCode.Empty;
197 else if ( DBNull.Value == value ) {
198 extendedCode = ExtendedClrTypeCode.DBNull;
203 case SqlDbType.BigInt:
204 if (value.GetType() == typeof(Int64))
205 extendedCode = ExtendedClrTypeCode.Int64;
206 else if (value.GetType() == typeof(SqlInt64))
207 extendedCode = ExtendedClrTypeCode.SqlInt64;
208 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int64)
209 extendedCode = ExtendedClrTypeCode.Int64;
211 case SqlDbType.Binary:
212 case SqlDbType.VarBinary:
213 case SqlDbType.Image:
214 case SqlDbType.Timestamp:
215 if (value.GetType() == typeof( byte[] ))
216 extendedCode = ExtendedClrTypeCode.ByteArray;
217 else if (value.GetType() == typeof( SqlBinary ))
218 extendedCode = ExtendedClrTypeCode.SqlBinary;
219 else if (value.GetType() == typeof( SqlBytes ))
220 extendedCode = ExtendedClrTypeCode.SqlBytes;
221 else if (value.GetType() == typeof(StreamDataFeed))
222 extendedCode = ExtendedClrTypeCode.Stream;
225 if (value.GetType() == typeof( bool ))
226 extendedCode = ExtendedClrTypeCode.Boolean;
227 else if (value.GetType() == typeof( SqlBoolean ))
228 extendedCode = ExtendedClrTypeCode.SqlBoolean;
229 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Boolean)
230 extendedCode = ExtendedClrTypeCode.Boolean;
233 case SqlDbType.NChar:
234 case SqlDbType.NText:
235 case SqlDbType.NVarChar:
237 case SqlDbType.VarChar:
238 if (value.GetType() == typeof( string ))
239 extendedCode = ExtendedClrTypeCode.String;
240 if (value.GetType() == typeof(TextDataFeed))
241 extendedCode = ExtendedClrTypeCode.TextReader;
242 else if (value.GetType() == typeof(SqlString))
243 extendedCode = ExtendedClrTypeCode.SqlString;
244 else if (value.GetType() == typeof(char[]))
245 extendedCode = ExtendedClrTypeCode.CharArray;
246 else if (value.GetType() == typeof(SqlChars))
247 extendedCode = ExtendedClrTypeCode.SqlChars;
248 else if (value.GetType() == typeof(char))
249 extendedCode = ExtendedClrTypeCode.Char;
250 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Char)
251 extendedCode = ExtendedClrTypeCode.Char;
252 else if (Type.GetTypeCode(value.GetType()) == TypeCode.String)
253 extendedCode = ExtendedClrTypeCode.String;
256 case SqlDbType.DateTime2:
257 if (smiVersion >= SmiContextFactory.KatmaiVersion) {
258 goto case SqlDbType.DateTime;
261 case SqlDbType.DateTime:
262 case SqlDbType.SmallDateTime:
263 if (value.GetType() == typeof( DateTime ))
264 extendedCode = ExtendedClrTypeCode.DateTime;
265 else if (value.GetType() == typeof( SqlDateTime ))
266 extendedCode = ExtendedClrTypeCode.SqlDateTime;
267 else if (Type.GetTypeCode(value.GetType()) == TypeCode.DateTime)
268 extendedCode = ExtendedClrTypeCode.DateTime;
270 case SqlDbType.Decimal:
271 if (value.GetType() == typeof( Decimal ))
272 extendedCode = ExtendedClrTypeCode.Decimal;
273 else if (value.GetType() == typeof( SqlDecimal ))
274 extendedCode = ExtendedClrTypeCode.SqlDecimal;
275 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Decimal)
276 extendedCode = ExtendedClrTypeCode.Decimal;
279 if (value.GetType() == typeof( Single ))
280 extendedCode = ExtendedClrTypeCode.Single;
281 else if (value.GetType() == typeof( SqlSingle ))
282 extendedCode = ExtendedClrTypeCode.SqlSingle;
283 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Single)
284 extendedCode = ExtendedClrTypeCode.Single;
287 if (value.GetType() == typeof( Int32 ))
288 extendedCode = ExtendedClrTypeCode.Int32;
289 else if (value.GetType() == typeof( SqlInt32 ))
290 extendedCode = ExtendedClrTypeCode.SqlInt32;
291 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int32)
292 extendedCode = ExtendedClrTypeCode.Int32;
294 case SqlDbType.Money:
295 case SqlDbType.SmallMoney:
296 if (value.GetType() == typeof( SqlMoney ))
297 extendedCode = ExtendedClrTypeCode.SqlMoney;
298 else if (value.GetType() == typeof( Decimal ))
299 extendedCode = ExtendedClrTypeCode.Decimal;
300 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Decimal)
301 extendedCode = ExtendedClrTypeCode.Decimal;
303 case SqlDbType.Float:
304 if (value.GetType() == typeof( SqlDouble ))
305 extendedCode = ExtendedClrTypeCode.SqlDouble;
306 else if (value.GetType() == typeof( Double ))
307 extendedCode = ExtendedClrTypeCode.Double;
308 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Double)
309 extendedCode = ExtendedClrTypeCode.Double;
311 case SqlDbType.UniqueIdentifier:
312 if (value.GetType() == typeof( SqlGuid ))
313 extendedCode = ExtendedClrTypeCode.SqlGuid;
314 else if (value.GetType() == typeof( Guid ))
315 extendedCode = ExtendedClrTypeCode.Guid;
317 case SqlDbType.SmallInt:
318 if (value.GetType() == typeof( Int16 ))
319 extendedCode = ExtendedClrTypeCode.Int16;
320 else if (value.GetType() == typeof( SqlInt16 ))
321 extendedCode = ExtendedClrTypeCode.SqlInt16;
322 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int16)
323 extendedCode = ExtendedClrTypeCode.Int16;
325 case SqlDbType.TinyInt:
326 if (value.GetType() == typeof( Byte ))
327 extendedCode = ExtendedClrTypeCode.Byte;
328 else if (value.GetType() == typeof( SqlByte ))
329 extendedCode = ExtendedClrTypeCode.SqlByte;
330 else if (Type.GetTypeCode(value.GetType()) == TypeCode.Byte)
331 extendedCode = ExtendedClrTypeCode.Byte;
333 case SqlDbType.Variant:
334 // SqlDbType doesn't help us here, call general-purpose function
335 extendedCode = DetermineExtendedTypeCode( value );
337 // Some types aren't allowed for Variants but are for the general-purpos function.
338 // Match behavior of other types and return invalid in these cases.
339 if ( ExtendedClrTypeCode.SqlXml == extendedCode ) {
340 extendedCode = ExtendedClrTypeCode.Invalid;
344 // Validate UDT type if caller gave us a type to validate against
345 if ( null == udtType ||
346 value.GetType() == udtType
348 extendedCode = ExtendedClrTypeCode.Object;
351 extendedCode = ExtendedClrTypeCode.Invalid;
355 if (value.GetType() == typeof(TimeSpan) && smiVersion >= SmiContextFactory.KatmaiVersion)
356 extendedCode = ExtendedClrTypeCode.TimeSpan;
358 case SqlDbType.DateTimeOffset:
359 if (value.GetType() == typeof(DateTimeOffset) && smiVersion >= SmiContextFactory.KatmaiVersion)
360 extendedCode = ExtendedClrTypeCode.DateTimeOffset;
363 if (value.GetType() == typeof( SqlXml ))
364 extendedCode = ExtendedClrTypeCode.SqlXml;
365 if (value.GetType() == typeof(XmlDataFeed))
366 extendedCode = ExtendedClrTypeCode.XmlReader;
367 else if (value.GetType() == typeof( System.String ))
368 extendedCode = ExtendedClrTypeCode.String;
370 case SqlDbType.Structured:
372 if (value is DataTable) {
373 extendedCode = ExtendedClrTypeCode.DataTable;
375 // Order is important, since some of these types are base types of the others.
376 // Evaluate from most derived to parent types
377 else if (value is IEnumerable<SqlDataRecord>) {
378 extendedCode = ExtendedClrTypeCode.IEnumerableOfSqlDataRecord;
380 else if (value is DbDataReader) {
381 extendedCode = ExtendedClrTypeCode.DbDataReader;
395 // Method to map from Type to ExtendedTypeCode
396 static internal ExtendedClrTypeCode DetermineExtendedTypeCodeFromType(Type clrType) {
397 object result = __typeToExtendedTypeCodeMap[clrType];
399 ExtendedClrTypeCode resultCode;
400 if ( null == result ) {
401 resultCode = ExtendedClrTypeCode.Invalid;
404 resultCode = (ExtendedClrTypeCode) result;
410 // Returns the ExtendedClrTypeCode that describes the given value
423 static internal ExtendedClrTypeCode DetermineExtendedTypeCode( object value ) {
424 ExtendedClrTypeCode resultCode;
425 if ( null == value ) {
426 resultCode = ExtendedClrTypeCode.Empty;
429 resultCode = DetermineExtendedTypeCodeFromType(value.GetType());
435 // returns a sqldbtype for the given type code
436 static internal SqlDbType InferSqlDbTypeFromTypeCode( ExtendedClrTypeCode typeCode ) {
437 Debug.Assert( typeCode >= ExtendedClrTypeCode.Invalid && typeCode <= ExtendedClrTypeCode.Last, "Someone added a typecode without adding support here!" );
439 return __extendedTypeCodeToSqlDbTypeMap[ (int) typeCode+1 ];
442 // Infer SqlDbType from Type in the general case. Katmai-only (or later) features that need to
443 // infer types should use InferSqlDbTypeFromType_Katmai.
444 static internal SqlDbType InferSqlDbTypeFromType(Type type) {
445 ExtendedClrTypeCode typeCode = DetermineExtendedTypeCodeFromType(type);
446 SqlDbType returnType;
447 if (ExtendedClrTypeCode.Invalid == typeCode) {
448 returnType = InvalidSqlDbType; // Return invalid type so caller can generate specific error
451 returnType = InferSqlDbTypeFromTypeCode(typeCode);
457 // Inference rules changed for Katmai-or-later-only cases. Only features that are guaranteed to be
458 // running against Katmai and don't have backward compat issues should call this code path.
459 // example: TVP's are a new Katmai feature (no back compat issues) so can infer DATETIME2
460 // when mapping System.DateTime from DateTable or DbDataReader. DATETIME2 is better because
461 // of greater range that can handle all DateTime values.
462 static internal SqlDbType InferSqlDbTypeFromType_Katmai(Type type) {
463 SqlDbType returnType = InferSqlDbTypeFromType(type);
464 if (SqlDbType.DateTime == returnType) {
465 returnType = SqlDbType.DateTime2;
470 static internal bool IsValidForSmiVersion(SmiExtendedMetaData md, ulong smiVersion) {
471 if (SmiContextFactory.LatestVersion == smiVersion) {
475 // Yukon doesn't support Structured nor the new time types
476 Debug.Assert(SmiContextFactory.YukonVersion == smiVersion, "Other versions should have been eliminated during link stage");
477 return md.SqlDbType != SqlDbType.Structured &&
478 md.SqlDbType != SqlDbType.Date &&
479 md.SqlDbType != SqlDbType.DateTime2 &&
480 md.SqlDbType != SqlDbType.DateTimeOffset &&
481 md.SqlDbType != SqlDbType.Time;
485 static internal SqlMetaData SmiExtendedMetaDataToSqlMetaData(SmiExtendedMetaData source) {
486 if (SqlDbType.Xml == source.SqlDbType) {
487 return new SqlMetaData(source.Name,
493 source.CompareOptions,
494 source.TypeSpecificNamePart1,
495 source.TypeSpecificNamePart2,
496 source.TypeSpecificNamePart3,
501 return new SqlMetaData(source.Name,
507 source.CompareOptions,
511 // Convert SqlMetaData instance to an SmiExtendedMetaData instance.
513 internal static SmiExtendedMetaData SqlMetaDataToSmiExtendedMetaData( SqlMetaData source ) {
514 // now map everything across to the extended metadata object
515 string typeSpecificNamePart1 = null;
516 string typeSpecificNamePart2 = null;
517 string typeSpecificNamePart3 = null;
519 if (SqlDbType.Xml == source.SqlDbType) {
520 typeSpecificNamePart1 = source.XmlSchemaCollectionDatabase;
521 typeSpecificNamePart2 = source.XmlSchemaCollectionOwningSchema;
522 typeSpecificNamePart3 = source.XmlSchemaCollectionName;
524 else if (SqlDbType.Udt == source.SqlDbType) {
525 // Split the input name. UdtTypeName is specified as single 3 part name.
526 // NOTE: ParseUdtTypeName throws if format is incorrect
527 string typeName = source.ServerTypeName;
528 if (null != typeName) {
529 String[] names = SqlParameter.ParseTypeName(typeName, true /* is for UdtTypeName */);
531 if (1 == names.Length) {
532 typeSpecificNamePart3 = names[0];
534 else if (2 == names.Length) {
535 typeSpecificNamePart2 = names[0];
536 typeSpecificNamePart3 = names[1];
538 else if (3 == names.Length) {
539 typeSpecificNamePart1 = names[0];
540 typeSpecificNamePart2 = names[1];
541 typeSpecificNamePart3 = names[2];
544 throw ADP.ArgumentOutOfRange("typeName");
547 if ((!ADP.IsEmpty(typeSpecificNamePart1) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart1.Length)
548 || (!ADP.IsEmpty(typeSpecificNamePart2) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart2.Length)
549 || (!ADP.IsEmpty(typeSpecificNamePart3) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart3.Length)) {
550 throw ADP.ArgumentOutOfRange("typeName");
555 return new SmiExtendedMetaData( source.SqlDbType,
560 source.CompareOptions,
563 typeSpecificNamePart1,
564 typeSpecificNamePart2,
565 typeSpecificNamePart3 );
571 // compare SmiMetaData to SqlMetaData and determine if they are compatible.
572 static internal bool IsCompatible(SmiMetaData firstMd, SqlMetaData secondMd) {
573 return firstMd.SqlDbType == secondMd.SqlDbType &&
574 firstMd.MaxLength == secondMd.MaxLength &&
575 firstMd.Precision == secondMd.Precision &&
576 firstMd.Scale == secondMd.Scale &&
577 firstMd.CompareOptions == secondMd.CompareOptions &&
578 firstMd.LocaleId == secondMd.LocaleId &&
579 firstMd.Type == secondMd.Type &&
580 firstMd.SqlDbType != SqlDbType.Structured && // SqlMetaData doesn't support Structured types
581 !firstMd.IsMultiValued; // SqlMetaData doesn't have a "multivalued" option
584 static internal long AdjustMaxLength(SqlDbType dbType, long maxLength) {
585 if (SmiMetaData.UnlimitedMaxLengthIndicator != maxLength) {
587 maxLength = InvalidMaxLength;
591 case SqlDbType.Binary:
592 if (maxLength > SmiMetaData.MaxBinaryLength) {
593 maxLength = InvalidMaxLength;
597 if (maxLength > SmiMetaData.MaxANSICharacters) {
598 maxLength = InvalidMaxLength;
601 case SqlDbType.NChar:
602 if (maxLength > SmiMetaData.MaxUnicodeCharacters) {
603 maxLength = InvalidMaxLength;
606 case SqlDbType.NVarChar:
607 // Promote to MAX type if it won't fit in a normal type
608 if (SmiMetaData.MaxUnicodeCharacters < maxLength) {
609 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
612 case SqlDbType.VarBinary:
613 // Promote to MAX type if it won't fit in a normal type
614 if (SmiMetaData.MaxBinaryLength < maxLength) {
615 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
618 case SqlDbType.VarChar:
619 // Promote to MAX type if it won't fit in a normal type
620 if (SmiMetaData.MaxANSICharacters < maxLength) {
621 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
632 // Extract metadata for a single DataColumn
633 static internal SmiExtendedMetaData SmiMetaDataFromDataColumn(DataColumn column, DataTable parent) {
634 SqlDbType dbType = InferSqlDbTypeFromType_Katmai(column.DataType);
635 if (InvalidSqlDbType == dbType) {
636 throw SQL.UnsupportedColumnTypeForSqlProvider(column.ColumnName, column.DataType.Name);
639 long maxLength = AdjustMaxLength(dbType, column.MaxLength);
640 if (InvalidMaxLength == maxLength) {
641 throw SQL.InvalidColumnMaxLength(column.ColumnName, maxLength);
646 if (column.DataType == typeof(SqlDecimal)) {
648 // Must scan all values in column to determine best-fit precision & scale
649 Debug.Assert(null != parent);
651 byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
652 foreach (DataRow row in parent.Rows) {
653 object obj = row[column];
654 if (!(obj is DBNull)) {
655 SqlDecimal value = (SqlDecimal) obj;
657 byte tempNonFractPrec = checked((byte) (value.Precision - value.Scale));
658 if (tempNonFractPrec > nonFractionalPrecision) {
659 nonFractionalPrecision = tempNonFractPrec;
662 if (value.Scale > scale) {
669 precision = checked((byte)(nonFractionalPrecision + scale));
671 if (SqlDecimal.MaxPrecision < precision) {
672 throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
674 else if (0 == precision) {
678 else if (dbType == SqlDbType.DateTime2 || dbType == SqlDbType.DateTimeOffset || dbType == SqlDbType.Time) {
679 // Time types care about scale, too. But have to infer maximums for these.
681 scale = SmiMetaData.DefaultTime.Scale;
683 else if (dbType == SqlDbType.Decimal) {
684 // Must scan all values in column to determine best-fit precision & scale
685 Debug.Assert(null != parent);
687 byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
688 foreach (DataRow row in parent.Rows) {
689 object obj = row[column];
690 if (!(obj is DBNull)) {
691 SqlDecimal value = (SqlDecimal)(Decimal)obj;
692 byte tempNonFractPrec = checked((byte)(value.Precision - value.Scale));
693 if (tempNonFractPrec > nonFractionalPrecision) {
694 nonFractionalPrecision = tempNonFractPrec;
697 if (value.Scale > scale) {
703 precision = checked((byte)(nonFractionalPrecision + scale));
705 if (SqlDecimal.MaxPrecision < precision) {
706 throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
708 else if (0 == precision) {
717 return new SmiExtendedMetaData(
723 SmiMetaData.DefaultNVarChar.CompareOptions,
725 false, // no support for multi-valued columns in a TVP yet
726 null, // no support for structured columns yet
727 null, // no support for structured columns yet
734 // Map SmiMetaData from a schema table.
735 // DEVNOTE: since we're using SchemaTable, we can assume that we aren't directly using a SqlDataReader
736 // so we don't support the Sql-specific stuff, like collation
737 static internal SmiExtendedMetaData SmiMetaDataFromSchemaTableRow(DataRow schemaRow) {
738 // One way or another, we'll need column name, so put it in a local now to shorten code later.
740 object temp = schemaRow[SchemaTableColumn.ColumnName];
741 if (DBNull.Value != temp) {
742 colName = (string)temp;
745 // Determine correct SqlDbType.
746 temp = schemaRow[SchemaTableColumn.DataType];
747 if (DBNull.Value == temp) {
748 throw SQL.NullSchemaTableDataTypeNotSupported(colName);
750 Type colType = (Type)temp;
751 SqlDbType colDbType = InferSqlDbTypeFromType_Katmai(colType);
752 if (InvalidSqlDbType == colDbType) {
753 // Unknown through standard mapping, use VarBinary for columns that are Object typed, otherwise error
754 if (typeof(object) == colType) {
755 colDbType = SqlDbType.VarBinary;
758 throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
762 // Determine metadata modifier values per type (maxlength, precision, scale, etc)
767 case SqlDbType.BigInt:
769 case SqlDbType.DateTime:
770 case SqlDbType.Float:
771 case SqlDbType.Image:
773 case SqlDbType.Money:
774 case SqlDbType.NText:
776 case SqlDbType.UniqueIdentifier:
777 case SqlDbType.SmallDateTime:
778 case SqlDbType.SmallInt:
779 case SqlDbType.SmallMoney:
781 case SqlDbType.Timestamp:
782 case SqlDbType.TinyInt:
783 case SqlDbType.Variant:
786 // These types require no metadata modifies
788 case SqlDbType.Binary:
789 case SqlDbType.VarBinary:
790 // These types need a binary max length
791 temp = schemaRow[SchemaTableColumn.ColumnSize];
792 if (DBNull.Value == temp) {
793 // source isn't specifying a size, so assume the worst
794 if (SqlDbType.Binary == colDbType) {
795 maxLength = SmiMetaData.MaxBinaryLength;
798 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
802 // We (should) have a valid maxlength, so use it.
803 maxLength = Convert.ToInt64(temp, null);
805 // Max length must be 0 to MaxBinaryLength or it can be UnlimitedMAX if type is varbinary
806 // If it's greater than MaxBinaryLength, just promote it to UnlimitedMAX, if possible
807 if (maxLength > SmiMetaData.MaxBinaryLength) {
808 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
811 if ((maxLength < 0 &&
812 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
813 SqlDbType.Binary == colDbType))) {
814 throw SQL.InvalidColumnMaxLength(colName, maxLength);
819 case SqlDbType.VarChar:
820 // These types need an ANSI max length
821 temp = schemaRow[SchemaTableColumn.ColumnSize];
822 if (DBNull.Value == temp) {
823 // source isn't specifying a size, so assume the worst
824 if (SqlDbType.Char == colDbType) {
825 maxLength = SmiMetaData.MaxANSICharacters;
828 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
832 // We (should) have a valid maxlength, so use it.
833 maxLength = Convert.ToInt64(temp, null);
835 // Max length must be 0 to MaxANSICharacters or it can be UnlimitedMAX if type is varbinary
836 // If it's greater than MaxANSICharacters, just promote it to UnlimitedMAX, if possible
837 if (maxLength > SmiMetaData.MaxANSICharacters) {
838 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
841 if ((maxLength < 0 &&
842 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
843 SqlDbType.Char == colDbType))) {
844 throw SQL.InvalidColumnMaxLength(colName, maxLength);
848 case SqlDbType.NChar:
849 case SqlDbType.NVarChar:
850 // These types need a unicode max length
851 temp = schemaRow[SchemaTableColumn.ColumnSize];
852 if (DBNull.Value == temp) {
853 // source isn't specifying a size, so assume the worst
854 if (SqlDbType.NChar == colDbType) {
855 maxLength = SmiMetaData.MaxUnicodeCharacters;
858 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
862 // We (should) have a valid maxlength, so use it.
863 maxLength = Convert.ToInt64(temp, null);
865 // Max length must be 0 to MaxUnicodeCharacters or it can be UnlimitedMAX if type is varbinary
866 // If it's greater than MaxUnicodeCharacters, just promote it to UnlimitedMAX, if possible
867 if (maxLength > SmiMetaData.MaxUnicodeCharacters) {
868 maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
871 if ((maxLength < 0 &&
872 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
873 SqlDbType.NChar == colDbType))) {
874 throw SQL.InvalidColumnMaxLength(colName, maxLength);
878 case SqlDbType.Decimal:
879 // Decimal requires precision and scale
880 temp = schemaRow[SchemaTableColumn.NumericPrecision];
881 if (DBNull.Value == temp) {
882 precision = SmiMetaData.DefaultDecimal.Precision;
885 precision = Convert.ToByte(temp, null);
888 temp = schemaRow[SchemaTableColumn.NumericScale];
889 if (DBNull.Value == temp) {
890 scale = SmiMetaData.DefaultDecimal.Scale;
893 scale = Convert.ToByte(temp, null);
896 if (precision < SmiMetaData.MinPrecision ||
897 precision > SqlDecimal.MaxPrecision ||
898 scale < SmiMetaData.MinScale ||
899 scale > SqlDecimal.MaxScale ||
901 throw SQL.InvalidColumnPrecScale();
905 case SqlDbType.DateTime2:
906 case SqlDbType.DateTimeOffset:
908 temp = schemaRow[SchemaTableColumn.NumericScale];
909 if (DBNull.Value == temp) {
910 scale = SmiMetaData.DefaultTime.Scale;
913 scale = Convert.ToByte(temp, null);
916 if (scale > SmiMetaData.MaxTimeScale) {
917 throw SQL.InvalidColumnPrecScale();
919 else if (scale < 0) {
920 scale = SmiMetaData.DefaultTime.Scale;
924 case SqlDbType.Structured:
926 // These types are not supported from SchemaTable
927 throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
930 return new SmiExtendedMetaData(
935 System.Globalization.CultureInfo.CurrentCulture.LCID,
936 SmiMetaData.GetDefaultForType(colDbType).CompareOptions,
937 null, // no support for UDTs from SchemaTable
938 false, // no support for multi-valued columns in a TVP yet
939 null, // no support for structured columns yet
940 null, // no support for structured columns yet