Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data / Microsoft / SqlServer / Server / MetadataUtilsSmi.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="MetaDataUtilsSmi.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 namespace Microsoft.SqlServer.Server {
10
11     using System;
12     using System.Collections;
13     using System.Collections.Generic;
14     using System.Data;
15     using System.Data.Sql;
16     using System.Data.Common;
17     using System.Data.SqlClient;
18     using System.Data.SqlTypes;
19     using System.Diagnostics;
20
21
22     // Utilities for manipulating smi-related metadata.
23     //
24     //  THIS CLASS IS BUILT ON TOP OF THE SMI INTERFACE -- SMI SHOULD NOT DEPEND ON IT!
25     //
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 {
29
30         internal const SqlDbType    InvalidSqlDbType = (SqlDbType) (-1);
31         internal const long         InvalidMaxLength = -2;
32
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
81         };
82
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;
86
87
88         // class ctor
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;
136         }
137
138
139         internal static bool IsCharOrXmlType(SqlDbType type) {
140             return  IsUnicodeType(type) ||
141                     IsAnsiType(type) ||
142                     type == SqlDbType.Xml;
143         }
144
145         internal static bool IsUnicodeType(SqlDbType type) {
146             return  type == SqlDbType.NChar ||
147                     type == SqlDbType.NVarChar ||
148                     type == SqlDbType.NText;
149         }
150
151         internal static bool IsAnsiType(SqlDbType type) {
152             return  type == SqlDbType.Char ||
153                     type == SqlDbType.VarChar ||
154                     type == SqlDbType.Text;
155         }
156
157         internal static bool IsBinaryType(SqlDbType type) {
158             return  type == SqlDbType.Binary ||
159                     type == SqlDbType.VarBinary ||
160                     type == SqlDbType.Image;
161         }
162
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;
170         }
171
172
173
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(
186                 SqlDbType   dbType, 
187                 bool        isMultiValued, 
188                 object      value, 
189                 Type        udtType,
190                 ulong       smiVersion) {
191             ExtendedClrTypeCode extendedCode = ExtendedClrTypeCode.Invalid;
192
193             // fast-track null, which is valid for all types
194             if ( null == value ) {
195                 extendedCode = ExtendedClrTypeCode.Empty;
196             }
197             else if ( DBNull.Value == value ) {
198                 extendedCode = ExtendedClrTypeCode.DBNull;
199             }
200             else {
201                 switch(dbType)
202                     {
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;
210                         break;
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;
223                         break;
224                     case SqlDbType.Bit:
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;
231                         break;
232                     case SqlDbType.Char:
233                     case SqlDbType.NChar:
234                     case SqlDbType.NText:
235                     case SqlDbType.NVarChar:
236                     case SqlDbType.Text:
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;
254                         break;
255                     case SqlDbType.Date:
256                     case SqlDbType.DateTime2:
257                         if (smiVersion >= SmiContextFactory.KatmaiVersion) {
258                             goto case SqlDbType.DateTime;
259                         }
260                         break;
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;
269                         break;
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;
277                         break;
278                     case SqlDbType.Real:
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;
285                         break;
286                     case SqlDbType.Int:
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;
293                         break;
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;
302                         break;
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;
310                         break;
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;
316                         break;
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;
324                         break;
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;
332                         break;
333                     case SqlDbType.Variant:
334                         // SqlDbType doesn't help us here, call general-purpose function
335                         extendedCode = DetermineExtendedTypeCode( value );
336
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;
341                         }
342                         break;
343                     case SqlDbType.Udt:
344                         // Validate UDT type if caller gave us a type to validate against
345                         if ( null == udtType ||
346                                 value.GetType() == udtType
347                             ) {
348                             extendedCode = ExtendedClrTypeCode.Object;
349                         }
350                         else {
351                             extendedCode = ExtendedClrTypeCode.Invalid;
352                         }
353                         break;
354                     case SqlDbType.Time:
355                         if (value.GetType() == typeof(TimeSpan) && smiVersion >= SmiContextFactory.KatmaiVersion)
356                             extendedCode = ExtendedClrTypeCode.TimeSpan;
357                         break;
358                     case SqlDbType.DateTimeOffset:
359                         if (value.GetType() == typeof(DateTimeOffset) && smiVersion >= SmiContextFactory.KatmaiVersion)
360                             extendedCode = ExtendedClrTypeCode.DateTimeOffset;
361                         break;
362                     case SqlDbType.Xml:
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;
369                         break;
370                     case SqlDbType.Structured:
371                         if (isMultiValued) {
372                             if (value is DataTable) {
373                                 extendedCode = ExtendedClrTypeCode.DataTable;
374                             }
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;
379                             }
380                             else if (value is DbDataReader) {
381                                 extendedCode = ExtendedClrTypeCode.DbDataReader;
382                             }
383                         }
384                         break;
385                     default:
386                         // Leave as invalid
387                         break;
388                     }
389             }
390
391             return extendedCode;
392
393         }
394
395         // Method to map from Type to ExtendedTypeCode
396         static internal ExtendedClrTypeCode DetermineExtendedTypeCodeFromType(Type clrType) {
397             object result = __typeToExtendedTypeCodeMap[clrType];
398
399             ExtendedClrTypeCode resultCode;
400             if ( null == result ) {
401                 resultCode = ExtendedClrTypeCode.Invalid;
402             }
403             else {
404                 resultCode = (ExtendedClrTypeCode) result;
405             }
406
407             return resultCode;
408         }
409
410          // Returns the ExtendedClrTypeCode that describes the given value
411         //   
412
413
414
415
416
417
418
419
420
421
422
423         static internal ExtendedClrTypeCode DetermineExtendedTypeCode( object value ) {
424             ExtendedClrTypeCode resultCode;
425             if ( null == value ) {
426                 resultCode = ExtendedClrTypeCode.Empty;
427             }
428             else {
429                 resultCode = DetermineExtendedTypeCodeFromType(value.GetType());
430             }
431
432             return resultCode;
433         }
434
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!" );
438
439             return __extendedTypeCodeToSqlDbTypeMap[ (int) typeCode+1 ];
440         }
441
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
449             }
450             else {
451                 returnType = InferSqlDbTypeFromTypeCode(typeCode);
452             }
453
454             return returnType;
455         }
456
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;
466             }
467             return returnType;
468         }
469
470         static internal bool IsValidForSmiVersion(SmiExtendedMetaData md, ulong smiVersion) {
471             if (SmiContextFactory.LatestVersion == smiVersion) {
472                 return true;
473             }
474             else {
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;
482             }
483         }
484
485         static internal SqlMetaData SmiExtendedMetaDataToSqlMetaData(SmiExtendedMetaData source) {
486             if (SqlDbType.Xml == source.SqlDbType) {
487                 return new SqlMetaData(source.Name,
488                     source.SqlDbType,
489                     source.MaxLength,
490                     source.Precision,
491                     source.Scale,
492                     source.LocaleId,
493                     source.CompareOptions,
494                     source.TypeSpecificNamePart1,
495                     source.TypeSpecificNamePart2,
496                     source.TypeSpecificNamePart3,
497                     true,
498                     source.Type);
499             }
500
501             return new SqlMetaData(source.Name,
502                 source.SqlDbType,
503                 source.MaxLength,
504                 source.Precision,
505                 source.Scale,
506                 source.LocaleId,
507                 source.CompareOptions,
508                 source.Type);
509         }
510
511         // Convert SqlMetaData instance to an SmiExtendedMetaData instance.
512
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;
518             
519             if (SqlDbType.Xml == source.SqlDbType) {
520                 typeSpecificNamePart1 = source.XmlSchemaCollectionDatabase;
521                 typeSpecificNamePart2 = source.XmlSchemaCollectionOwningSchema;
522                 typeSpecificNamePart3 = source.XmlSchemaCollectionName;
523             }
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 */);
530
531                     if (1 == names.Length) {
532                         typeSpecificNamePart3 = names[0];
533                     }
534                     else if (2 == names.Length) {
535                         typeSpecificNamePart2 = names[0];
536                         typeSpecificNamePart3 = names[1];
537                     }
538                     else if (3 == names.Length) {
539                         typeSpecificNamePart1 = names[0];
540                         typeSpecificNamePart2 = names[1];
541                         typeSpecificNamePart3 = names[2];
542                     }
543                     else {
544                         throw ADP.ArgumentOutOfRange("typeName");
545                     }
546
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");
551                     }
552                 }
553             }                    
554
555             return new SmiExtendedMetaData( source.SqlDbType,
556                                             source.MaxLength,
557                                             source.Precision,
558                                             source.Scale,
559                                             source.LocaleId,
560                                             source.CompareOptions,
561                                             source.Type,
562                                             source.Name,
563                                             typeSpecificNamePart1,
564                                             typeSpecificNamePart2,
565                                             typeSpecificNamePart3 );
566
567
568         }
569
570
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
582         }
583
584         static internal long AdjustMaxLength(SqlDbType dbType, long maxLength) {
585             if (SmiMetaData.UnlimitedMaxLengthIndicator != maxLength) {
586                 if (maxLength < 0) {
587                     maxLength = InvalidMaxLength;
588                 }
589
590                 switch(dbType) {
591                     case SqlDbType.Binary:
592                         if (maxLength > SmiMetaData.MaxBinaryLength) {
593                             maxLength = InvalidMaxLength;
594                         }
595                         break;
596                     case SqlDbType.Char:
597                         if (maxLength > SmiMetaData.MaxANSICharacters) {
598                             maxLength = InvalidMaxLength;
599                         }
600                         break;
601                     case SqlDbType.NChar:
602                         if (maxLength > SmiMetaData.MaxUnicodeCharacters) {
603                             maxLength = InvalidMaxLength;
604                         }
605                         break;
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;
610                         }
611                         break;
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;
616                         }
617                         break;
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;
622                         }
623                         break;
624                     default:
625                         break;
626                 }
627             }
628
629             return maxLength;
630         }
631
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);
637             }
638
639             long maxLength = AdjustMaxLength(dbType, column.MaxLength);
640             if (InvalidMaxLength == maxLength) {
641                 throw SQL.InvalidColumnMaxLength(column.ColumnName, maxLength);
642             }
643
644             byte precision;
645             byte scale;
646             if (column.DataType == typeof(SqlDecimal)) {
647
648                 // Must scan all values in column to determine best-fit precision & scale
649                 Debug.Assert(null != parent);
650                 scale = 0;
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;
656                         if (!value.IsNull) {
657                             byte tempNonFractPrec = checked((byte) (value.Precision - value.Scale));
658                             if (tempNonFractPrec > nonFractionalPrecision) {
659                                 nonFractionalPrecision = tempNonFractPrec;
660                             }
661
662                             if (value.Scale > scale) {
663                                 scale = value.Scale;
664                             }
665                         }
666                     }
667                 }
668
669                 precision = checked((byte)(nonFractionalPrecision + scale));
670
671                 if (SqlDecimal.MaxPrecision < precision) {
672                     throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
673                 }
674                 else if (0 == precision) {
675                     precision = 1;
676                 }
677             }
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.
680                 precision = 0;
681                 scale = SmiMetaData.DefaultTime.Scale;
682             }
683             else if (dbType == SqlDbType.Decimal) {
684                 // Must scan all values in column to determine best-fit precision & scale
685                 Debug.Assert(null != parent);
686                 scale = 0;
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;
695                         }
696
697                         if (value.Scale > scale) {
698                             scale = value.Scale;
699                         }
700                     }
701                 }
702
703                 precision = checked((byte)(nonFractionalPrecision + scale));
704
705                 if (SqlDecimal.MaxPrecision < precision) {
706                     throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
707                 }
708                 else if (0 == precision) {
709                     precision = 1;
710                 }
711             }
712             else {
713                 precision = 0;
714                 scale = 0;
715             }
716
717             return new SmiExtendedMetaData(
718                                         dbType, 
719                                         maxLength, 
720                                         precision, 
721                                         scale, 
722                                         column.Locale.LCID, 
723                                         SmiMetaData.DefaultNVarChar.CompareOptions, 
724                                         column.DataType, 
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
728                                         column.ColumnName, 
729                                         null, 
730                                         null, 
731                                         null);
732         }
733
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.
739             string colName = "";
740             object temp = schemaRow[SchemaTableColumn.ColumnName];
741             if (DBNull.Value != temp) {
742                 colName = (string)temp;
743             }
744
745             // Determine correct SqlDbType.
746             temp = schemaRow[SchemaTableColumn.DataType];
747             if (DBNull.Value == temp) {
748                 throw SQL.NullSchemaTableDataTypeNotSupported(colName);
749             }
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;
756                 }
757                 else {
758                     throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
759                 }
760             }
761
762             // Determine metadata modifier values per type (maxlength, precision, scale, etc)
763             long maxLength = 0;
764             byte precision = 0;
765             byte scale = 0;
766             switch (colDbType) {
767                 case SqlDbType.BigInt:
768                 case SqlDbType.Bit:
769                 case SqlDbType.DateTime:
770                 case SqlDbType.Float:
771                 case SqlDbType.Image:
772                 case SqlDbType.Int:
773                 case SqlDbType.Money:
774                 case SqlDbType.NText:
775                 case SqlDbType.Real:
776                 case SqlDbType.UniqueIdentifier:
777                 case SqlDbType.SmallDateTime:
778                 case SqlDbType.SmallInt:
779                 case SqlDbType.SmallMoney:
780                 case SqlDbType.Text:
781                 case SqlDbType.Timestamp:
782                 case SqlDbType.TinyInt:
783                 case SqlDbType.Variant:
784                 case SqlDbType.Xml:
785                 case SqlDbType.Date:
786                     // These types require no  metadata modifies
787                     break;
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;
796                         }
797                         else {
798                             maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
799                         }
800                     }
801                     else {
802                         // We (should) have a valid maxlength, so use it.
803                         maxLength = Convert.ToInt64(temp, null);
804
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;
809                         }
810
811                         if ((maxLength < 0 &&
812                                 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
813                                  SqlDbType.Binary == colDbType))) {
814                             throw SQL.InvalidColumnMaxLength(colName, maxLength);
815                         }
816                     }
817                     break;
818                 case SqlDbType.Char:
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;
826                         }
827                         else {
828                             maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
829                         }
830                     }
831                     else {
832                         // We (should) have a valid maxlength, so use it.
833                         maxLength = Convert.ToInt64(temp, null);
834
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;
839                         }
840
841                         if ((maxLength < 0 && 
842                                 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
843                                  SqlDbType.Char == colDbType))) {
844                             throw SQL.InvalidColumnMaxLength(colName, maxLength);
845                         }
846                     }
847                     break;
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;
856                         }
857                         else {
858                             maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
859                         }
860                     }
861                     else {
862                         // We (should) have a valid maxlength, so use it.
863                         maxLength = Convert.ToInt64(temp, null);
864
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;
869                         }
870
871                         if ((maxLength < 0 &&
872                                 (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
873                                  SqlDbType.NChar == colDbType))) {
874                             throw SQL.InvalidColumnMaxLength(colName, maxLength);
875                         }
876                     }
877                     break;
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;
883                     }
884                     else {
885                         precision = Convert.ToByte(temp, null);
886                     }
887
888                     temp = schemaRow[SchemaTableColumn.NumericScale];
889                     if (DBNull.Value == temp) {
890                         scale = SmiMetaData.DefaultDecimal.Scale;
891                     }
892                     else {
893                         scale = Convert.ToByte(temp, null);
894                     }
895
896                     if (precision < SmiMetaData.MinPrecision || 
897                             precision > SqlDecimal.MaxPrecision || 
898                             scale < SmiMetaData.MinScale || 
899                             scale > SqlDecimal.MaxScale ||
900                             scale > precision) {
901                         throw SQL.InvalidColumnPrecScale();
902                     }
903                     break;
904                 case SqlDbType.Time:
905                 case SqlDbType.DateTime2:
906                 case SqlDbType.DateTimeOffset:
907                     // requires scale
908                     temp = schemaRow[SchemaTableColumn.NumericScale];
909                     if (DBNull.Value == temp) {
910                         scale = SmiMetaData.DefaultTime.Scale;
911                     }
912                     else {
913                         scale = Convert.ToByte(temp, null);
914                     }
915
916                     if (scale > SmiMetaData.MaxTimeScale) {
917                         throw SQL.InvalidColumnPrecScale();
918                     }
919                     else if (scale < 0) {
920                         scale = SmiMetaData.DefaultTime.Scale;
921                     }
922                     break;
923                 case SqlDbType.Udt:
924                 case SqlDbType.Structured:
925                 default:
926                     // These types are not supported from SchemaTable
927                     throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
928             }
929
930             return new SmiExtendedMetaData(
931                                         colDbType, 
932                                         maxLength, 
933                                         precision, 
934                                         scale, 
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
941                                         colName, 
942                                         null, 
943                                         null, 
944                                         null);
945         }
946     }
947 }