File: Microsoft\SqlServer\Server\MetadataUtilsSmi.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="MetaDataUtilsSmi.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace Microsoft.SqlServer.Server {
 
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Sql;
    using System.Data.Common;
    using System.Data.SqlClient;
    using System.Data.SqlTypes;
    using System.Diagnostics;
 
 
    // Utilities for manipulating smi-related metadata.
    //
    //  THIS CLASS IS BUILT ON TOP OF THE SMI INTERFACE -- SMI SHOULD NOT DEPEND ON IT!
    //
    //  These are all based off of knowing the clr type of the value
    //  as an ExtendedClrTypeCode enum for rapid access (lookup in static array is best, if possible).
    internal class MetaDataUtilsSmi {
 
        internal const SqlDbType    InvalidSqlDbType = (SqlDbType) (-1);
        internal const long         InvalidMaxLength = -2;
 
        // Standard type inference map to get SqlDbType when all you know is the value's type (typecode)
        //  This map's index is off by one (add one to typecode locate correct entry) in order 
        //  support ExtendedSqlDbType.Invalid
        // ONLY ACCESS THIS ARRAY FROM InferSqlDbTypeFromTypeCode!!!
        static readonly SqlDbType[] __extendedTypeCodeToSqlDbTypeMap = {
            InvalidSqlDbType,               // Invalid extended type code
            SqlDbType.Bit,                  // System.Boolean
            SqlDbType.TinyInt,              // System.Byte
            SqlDbType.NVarChar,             // System.Char
            SqlDbType.DateTime,             // System.DateTime
            InvalidSqlDbType,               // System.DBNull doesn't have an inferable SqlDbType
            SqlDbType.Decimal,              // System.Decimal
            SqlDbType.Float,                // System.Double
            InvalidSqlDbType,               // null reference doesn't have an inferable SqlDbType
            SqlDbType.SmallInt,             // System.Int16
            SqlDbType.Int,                  // System.Int32
            SqlDbType.BigInt,               // System.Int64
            InvalidSqlDbType,               // System.SByte doesn't have an inferable SqlDbType
            SqlDbType.Real,                 // System.Single
            SqlDbType.NVarChar,             // System.String
            InvalidSqlDbType,               // System.UInt16 doesn't have an inferable SqlDbType
            InvalidSqlDbType,               // System.UInt32 doesn't have an inferable SqlDbType
            InvalidSqlDbType,               // System.UInt64 doesn't have an inferable SqlDbType
            InvalidSqlDbType,               // System.Object doesn't have an inferable SqlDbType
            SqlDbType.VarBinary,            // System.ByteArray
            SqlDbType.NVarChar,             // System.CharArray
            SqlDbType.UniqueIdentifier,     // System.Guid
            SqlDbType.VarBinary,            // System.Data.SqlTypes.SqlBinary
            SqlDbType.Bit,                  // System.Data.SqlTypes.SqlBoolean
            SqlDbType.TinyInt,              // System.Data.SqlTypes.SqlByte
            SqlDbType.DateTime,             // System.Data.SqlTypes.SqlDateTime
            SqlDbType.Float,                // System.Data.SqlTypes.SqlDouble
            SqlDbType.UniqueIdentifier,     // System.Data.SqlTypes.SqlGuid
            SqlDbType.SmallInt,             // System.Data.SqlTypes.SqlInt16
            SqlDbType.Int,                  // System.Data.SqlTypes.SqlInt32
            SqlDbType.BigInt,               // System.Data.SqlTypes.SqlInt64
            SqlDbType.Money,                // System.Data.SqlTypes.SqlMoney
            SqlDbType.Decimal,              // System.Data.SqlTypes.SqlDecimal
            SqlDbType.Real,                 // System.Data.SqlTypes.SqlSingle
            SqlDbType.NVarChar,             // System.Data.SqlTypes.SqlString
            SqlDbType.NVarChar,             // System.Data.SqlTypes.SqlChars
            SqlDbType.VarBinary,            // System.Data.SqlTypes.SqlBytes
            SqlDbType.Xml,                  // System.Data.SqlTypes.SqlXml
            SqlDbType.Structured,           // System.Data.DataTable
            SqlDbType.Structured,           // System.Collections.IEnumerable, used for TVPs it must return IDataRecord
            SqlDbType.Structured,           // System.Collections.Generic.IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>
            SqlDbType.Time,                 // System.TimeSpan
            SqlDbType.DateTimeOffset,       // System.DateTimeOffset
        };
 
        // Hash table to map from clr type object to ExtendedClrTypeCodeMap enum
        //  ONLY ACCESS THIS HASH TABLE FROM DetermineExtendedTypeCode METHOD!!!  (and class ctor for setup)
        static readonly Hashtable __typeToExtendedTypeCodeMap;
 
 
        // class ctor
        static MetaDataUtilsSmi() {
            // set up type mapping hash table
            //  keep this initialization list in the same order as ExtendedClrTypeCode for ease in validating!
            Hashtable ht = new Hashtable(42);
            ht.Add( typeof( System.Boolean ),       ExtendedClrTypeCode.Boolean );
            ht.Add( typeof( System.Byte ),          ExtendedClrTypeCode.Byte );
            ht.Add( typeof( System.Char ),          ExtendedClrTypeCode.Char );
            ht.Add( typeof( System.DateTime ),      ExtendedClrTypeCode.DateTime );
            ht.Add( typeof( System.DBNull ),        ExtendedClrTypeCode.DBNull );
            ht.Add( typeof( System.Decimal ),       ExtendedClrTypeCode.Decimal );
            ht.Add( typeof( System.Double ),        ExtendedClrTypeCode.Double );
            // lookup code will have to special-case null-ref anyway, so don't bother adding ExtendedTypeCode.Empty to the table
            ht.Add( typeof( System.Int16 ),         ExtendedClrTypeCode.Int16 );
            ht.Add( typeof( System.Int32 ),         ExtendedClrTypeCode.Int32 );
            ht.Add( typeof( System.Int64 ),         ExtendedClrTypeCode.Int64 );
            ht.Add( typeof( System.SByte ),         ExtendedClrTypeCode.SByte );
            ht.Add( typeof( System.Single ),        ExtendedClrTypeCode.Single );
            ht.Add( typeof( System.String ),        ExtendedClrTypeCode.String );
            ht.Add( typeof( System.UInt16 ),        ExtendedClrTypeCode.UInt16 );
            ht.Add( typeof( System.UInt32 ),        ExtendedClrTypeCode.UInt32 );
            ht.Add( typeof( System.UInt64 ),        ExtendedClrTypeCode.UInt64 );
            ht.Add( typeof( System.Object ),        ExtendedClrTypeCode.Object );
            ht.Add( typeof( System.Byte[] ),        ExtendedClrTypeCode.ByteArray );
            ht.Add( typeof( System.Char[] ),        ExtendedClrTypeCode.CharArray );
            ht.Add( typeof( System.Guid ),          ExtendedClrTypeCode.Guid );
            ht.Add( typeof( SqlBinary ),            ExtendedClrTypeCode.SqlBinary );
            ht.Add( typeof( SqlBoolean ),           ExtendedClrTypeCode.SqlBoolean );
            ht.Add( typeof( SqlByte ),              ExtendedClrTypeCode.SqlByte );
            ht.Add( typeof( SqlDateTime ),          ExtendedClrTypeCode.SqlDateTime );
            ht.Add( typeof( SqlDouble ),            ExtendedClrTypeCode.SqlDouble );
            ht.Add( typeof( SqlGuid ),              ExtendedClrTypeCode.SqlGuid );
            ht.Add( typeof( SqlInt16 ),             ExtendedClrTypeCode.SqlInt16 );
            ht.Add( typeof( SqlInt32 ),             ExtendedClrTypeCode.SqlInt32 );
            ht.Add( typeof( SqlInt64 ),             ExtendedClrTypeCode.SqlInt64 );
            ht.Add( typeof( SqlMoney ),             ExtendedClrTypeCode.SqlMoney );
            ht.Add( typeof( SqlDecimal ),           ExtendedClrTypeCode.SqlDecimal );
            ht.Add( typeof( SqlSingle ),            ExtendedClrTypeCode.SqlSingle );
            ht.Add( typeof( SqlString ),            ExtendedClrTypeCode.SqlString );
            ht.Add( typeof( SqlChars ),             ExtendedClrTypeCode.SqlChars );
            ht.Add( typeof( SqlBytes ),             ExtendedClrTypeCode.SqlBytes );
            ht.Add( typeof( SqlXml ),               ExtendedClrTypeCode.SqlXml );
            ht.Add( typeof( DataTable ),            ExtendedClrTypeCode.DataTable );
            ht.Add( typeof( DbDataReader ),         ExtendedClrTypeCode.DbDataReader );
            ht.Add( typeof( IEnumerable<SqlDataRecord> ),          ExtendedClrTypeCode.IEnumerableOfSqlDataRecord );
            ht.Add( typeof( System.TimeSpan ),      ExtendedClrTypeCode.TimeSpan );
            ht.Add( typeof( System.DateTimeOffset ),               ExtendedClrTypeCode.DateTimeOffset );
            __typeToExtendedTypeCodeMap = ht;
        }
 
 
        internal static bool IsCharOrXmlType(SqlDbType type) {
            return  IsUnicodeType(type) ||
                    IsAnsiType(type) ||
                    type == SqlDbType.Xml;
        }
 
        internal static bool IsUnicodeType(SqlDbType type) {
            return  type == SqlDbType.NChar ||
                    type == SqlDbType.NVarChar ||
                    type == SqlDbType.NText;
        }
 
        internal static bool IsAnsiType(SqlDbType type) {
            return  type == SqlDbType.Char ||
                    type == SqlDbType.VarChar ||
                    type == SqlDbType.Text;
        }
 
        internal static bool IsBinaryType(SqlDbType type) {
            return  type == SqlDbType.Binary ||
                    type == SqlDbType.VarBinary ||
                    type == SqlDbType.Image;
        }
 
        // Does this type use PLP format values?
        internal static bool IsPlpFormat(SmiMetaData metaData) {
            return  metaData.MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator ||
                    metaData.SqlDbType == SqlDbType.Image ||
                    metaData.SqlDbType == SqlDbType.NText ||
                    metaData.SqlDbType == SqlDbType.Text ||
                    metaData.SqlDbType == SqlDbType.Udt;
        }
 
 
 
        // If we know we're only going to use this object to assign to a specific SqlDbType back end object,
        //  we can save some processing time by only checking for the few valid types that can be assigned to the dbType.
        //  This assumes a switch statement over SqlDbType is faster than getting the ClrTypeCode and iterating over a
        //  series of if statements, or using a hash table. 
        // NOTE: the form of these checks is taking advantage of a feature of the JIT compiler that is supposed to
        //      optimize checks of the form '(xxx.GetType() == typeof( YYY ))'.  The JIT team claimed at one point that
        //      this doesn't even instantiate a Type instance, thus was the fastest method for individual comparisions.
        //      Given that there's a known SqlDbType, thus a minimal number of comparisions, it's likely this is faster
        //      than the other approaches considered (both GetType().GetTypeCode() switch and hash table using Type keys
        //      must instantiate a Type object.  The typecode switch also degenerates into a large if-then-else for
        //      all but the primitive clr types.
        internal static ExtendedClrTypeCode DetermineExtendedTypeCodeForUseWithSqlDbType(
                SqlDbType   dbType, 
                bool        isMultiValued, 
                object      value, 
                Type        udtType,
                ulong       smiVersion) {
            ExtendedClrTypeCode extendedCode = ExtendedClrTypeCode.Invalid;
 
            // fast-track null, which is valid for all types
            if ( null == value ) {
                extendedCode = ExtendedClrTypeCode.Empty;
            }
            else if ( DBNull.Value == value ) {
                extendedCode = ExtendedClrTypeCode.DBNull;
            }
            else {
                switch(dbType)
                    {
                    case SqlDbType.BigInt:
                        if (value.GetType() == typeof(Int64))
                            extendedCode = ExtendedClrTypeCode.Int64;
                        else if (value.GetType() == typeof(SqlInt64))
                            extendedCode = ExtendedClrTypeCode.SqlInt64;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int64)
                            extendedCode = ExtendedClrTypeCode.Int64;
                        break;
                    case SqlDbType.Binary:
                    case SqlDbType.VarBinary:
                    case SqlDbType.Image:
                    case SqlDbType.Timestamp:
                        if (value.GetType() == typeof( byte[] ))
                            extendedCode = ExtendedClrTypeCode.ByteArray;
                        else if (value.GetType() == typeof( SqlBinary ))
                            extendedCode = ExtendedClrTypeCode.SqlBinary;
                        else if (value.GetType() == typeof( SqlBytes ))
                            extendedCode = ExtendedClrTypeCode.SqlBytes;
                        else if (value.GetType() == typeof(StreamDataFeed))
                            extendedCode = ExtendedClrTypeCode.Stream;
                        break;
                    case SqlDbType.Bit:
                        if (value.GetType() == typeof( bool ))
                            extendedCode = ExtendedClrTypeCode.Boolean;
                        else if (value.GetType() == typeof( SqlBoolean ))
                            extendedCode = ExtendedClrTypeCode.SqlBoolean;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Boolean)
                            extendedCode = ExtendedClrTypeCode.Boolean;
                        break;
                    case SqlDbType.Char:
                    case SqlDbType.NChar:
                    case SqlDbType.NText:
                    case SqlDbType.NVarChar:
                    case SqlDbType.Text:
                    case SqlDbType.VarChar:
                        if (value.GetType() == typeof( string ))
                            extendedCode = ExtendedClrTypeCode.String;
                        if (value.GetType() == typeof(TextDataFeed))
                            extendedCode = ExtendedClrTypeCode.TextReader;                     
                        else if (value.GetType() == typeof(SqlString))
                            extendedCode = ExtendedClrTypeCode.SqlString;
                        else if (value.GetType() == typeof(char[]))
                            extendedCode = ExtendedClrTypeCode.CharArray;
                        else if (value.GetType() == typeof(SqlChars))
                            extendedCode = ExtendedClrTypeCode.SqlChars;
                        else if (value.GetType() == typeof(char))
                            extendedCode = ExtendedClrTypeCode.Char;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Char)
                            extendedCode = ExtendedClrTypeCode.Char;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.String)
                            extendedCode = ExtendedClrTypeCode.String;
                        break;
                    case SqlDbType.Date:
                    case SqlDbType.DateTime2:
                        if (smiVersion >= SmiContextFactory.KatmaiVersion) {
                            goto case SqlDbType.DateTime;
                        }
                        break;
                    case SqlDbType.DateTime:
                    case SqlDbType.SmallDateTime:
                        if (value.GetType() == typeof( DateTime ))
                            extendedCode = ExtendedClrTypeCode.DateTime;
                        else if (value.GetType() == typeof( SqlDateTime ))
                            extendedCode = ExtendedClrTypeCode.SqlDateTime;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.DateTime)
                            extendedCode = ExtendedClrTypeCode.DateTime;
                        break;
                    case SqlDbType.Decimal:
                        if (value.GetType() == typeof( Decimal ))
                            extendedCode = ExtendedClrTypeCode.Decimal;
                        else if (value.GetType() == typeof( SqlDecimal ))
                            extendedCode = ExtendedClrTypeCode.SqlDecimal;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Decimal)
                            extendedCode = ExtendedClrTypeCode.Decimal;
                        break;
                    case SqlDbType.Real:
                        if (value.GetType() == typeof( Single ))
                            extendedCode = ExtendedClrTypeCode.Single;
                        else if (value.GetType() == typeof( SqlSingle ))
                            extendedCode = ExtendedClrTypeCode.SqlSingle;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Single)
                            extendedCode = ExtendedClrTypeCode.Single;
                        break;
                    case SqlDbType.Int:
                        if (value.GetType() == typeof( Int32 ))
                            extendedCode = ExtendedClrTypeCode.Int32;
                        else if (value.GetType() == typeof( SqlInt32 ))
                            extendedCode = ExtendedClrTypeCode.SqlInt32;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int32)
                            extendedCode = ExtendedClrTypeCode.Int32;
                        break;
                    case SqlDbType.Money:
                    case SqlDbType.SmallMoney:
                        if (value.GetType() == typeof( SqlMoney ))
                            extendedCode = ExtendedClrTypeCode.SqlMoney;
                        else if (value.GetType() == typeof( Decimal ))
                            extendedCode = ExtendedClrTypeCode.Decimal;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Decimal)
                            extendedCode = ExtendedClrTypeCode.Decimal;
                        break;
                    case SqlDbType.Float:
                        if (value.GetType() == typeof( SqlDouble ))
                            extendedCode = ExtendedClrTypeCode.SqlDouble;
                        else if (value.GetType() == typeof( Double ))
                            extendedCode = ExtendedClrTypeCode.Double;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Double)
                            extendedCode = ExtendedClrTypeCode.Double;
                        break;
                    case SqlDbType.UniqueIdentifier:
                        if (value.GetType() == typeof( SqlGuid ))
                            extendedCode = ExtendedClrTypeCode.SqlGuid;
                        else if (value.GetType() == typeof( Guid ))
                            extendedCode = ExtendedClrTypeCode.Guid;
                        break;
                    case SqlDbType.SmallInt:
                        if (value.GetType() == typeof( Int16 ))
                            extendedCode = ExtendedClrTypeCode.Int16;
                        else if (value.GetType() == typeof( SqlInt16 ))
                            extendedCode = ExtendedClrTypeCode.SqlInt16;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Int16)
                            extendedCode = ExtendedClrTypeCode.Int16;
                        break;
                    case SqlDbType.TinyInt:
                        if (value.GetType() == typeof( Byte ))
                            extendedCode = ExtendedClrTypeCode.Byte;
                        else if (value.GetType() == typeof( SqlByte ))
                            extendedCode = ExtendedClrTypeCode.SqlByte;
                        else if (Type.GetTypeCode(value.GetType()) == TypeCode.Byte)
                            extendedCode = ExtendedClrTypeCode.Byte;
                        break;
                    case SqlDbType.Variant:
                        // SqlDbType doesn't help us here, call general-purpose function
                        extendedCode = DetermineExtendedTypeCode( value );
 
                        // Some types aren't allowed for Variants but are for the general-purpos function.  
                        //  Match behavior of other types and return invalid in these cases.
                        if ( ExtendedClrTypeCode.SqlXml == extendedCode ) {
                            extendedCode = ExtendedClrTypeCode.Invalid;
                        }
                        break;
                    case SqlDbType.Udt:
                        // Validate UDT type if caller gave us a type to validate against
                        if ( null == udtType ||
                                value.GetType() == udtType
                            ) {
                            extendedCode = ExtendedClrTypeCode.Object;
                        }
                        else {
                            extendedCode = ExtendedClrTypeCode.Invalid;
                        }
                        break;
                    case SqlDbType.Time:
                        if (value.GetType() == typeof(TimeSpan) && smiVersion >= SmiContextFactory.KatmaiVersion)
                            extendedCode = ExtendedClrTypeCode.TimeSpan;
                        break;
                    case SqlDbType.DateTimeOffset:
                        if (value.GetType() == typeof(DateTimeOffset) && smiVersion >= SmiContextFactory.KatmaiVersion)
                            extendedCode = ExtendedClrTypeCode.DateTimeOffset;
                        break;
                    case SqlDbType.Xml:
                        if (value.GetType() == typeof( SqlXml ))
                            extendedCode = ExtendedClrTypeCode.SqlXml;
                        if (value.GetType() == typeof(XmlDataFeed))
                            extendedCode = ExtendedClrTypeCode.XmlReader;
                        else if (value.GetType() == typeof( System.String ))
                            extendedCode = ExtendedClrTypeCode.String;
                        break;
                    case SqlDbType.Structured:
                        if (isMultiValued) {
                            if (value is DataTable) {
                                extendedCode = ExtendedClrTypeCode.DataTable;
                            }
                            // Order is important, since some of these types are base types of the others.
                            //  Evaluate from most derived to parent types
                            else if (value is IEnumerable<SqlDataRecord>) {
                                extendedCode = ExtendedClrTypeCode.IEnumerableOfSqlDataRecord;
                            }
                            else if (value is DbDataReader) {
                                extendedCode = ExtendedClrTypeCode.DbDataReader;
                            }
                        }
                        break;
                    default:
                        // Leave as invalid
                        break;
                    }
            }
 
            return extendedCode;
 
        }
 
        // Method to map from Type to ExtendedTypeCode
        static internal ExtendedClrTypeCode DetermineExtendedTypeCodeFromType(Type clrType) {
            object result = __typeToExtendedTypeCodeMap[clrType];
 
            ExtendedClrTypeCode resultCode;
            if ( null == result ) {
                resultCode = ExtendedClrTypeCode.Invalid;
            }
            else {
                resultCode = (ExtendedClrTypeCode) result;
            }
 
            return resultCode;
        }
 
         // Returns the ExtendedClrTypeCode that describes the given value
        //   
 
 
 
 
 
 
 
 
 
 
 
        static internal ExtendedClrTypeCode DetermineExtendedTypeCode( object value ) {
            ExtendedClrTypeCode resultCode;
            if ( null == value ) {
                resultCode = ExtendedClrTypeCode.Empty;
            }
            else {
                resultCode = DetermineExtendedTypeCodeFromType(value.GetType());
            }
 
            return resultCode;
        }
 
        // returns a sqldbtype for the given type code
        static internal SqlDbType InferSqlDbTypeFromTypeCode( ExtendedClrTypeCode typeCode ) {
            Debug.Assert( typeCode >= ExtendedClrTypeCode.Invalid && typeCode <= ExtendedClrTypeCode.Last, "Someone added a typecode without adding support here!" );
 
            return __extendedTypeCodeToSqlDbTypeMap[ (int) typeCode+1 ];
        }
 
        // Infer SqlDbType from Type in the general case.  Katmai-only (or later) features that need to 
        //  infer types should use InferSqlDbTypeFromType_Katmai.
        static internal SqlDbType InferSqlDbTypeFromType(Type type) {
            ExtendedClrTypeCode typeCode = DetermineExtendedTypeCodeFromType(type);
            SqlDbType returnType;
            if (ExtendedClrTypeCode.Invalid == typeCode) {
                returnType = InvalidSqlDbType;  // Return invalid type so caller can generate specific error
            }
            else {
                returnType = InferSqlDbTypeFromTypeCode(typeCode);
            }
 
            return returnType;
        }
 
        // Inference rules changed for Katmai-or-later-only cases.  Only features that are guaranteed to be 
        //  running against Katmai and don't have backward compat issues should call this code path.
        //      example: TVP's are a new Katmai feature (no back compat issues) so can infer DATETIME2
        //          when mapping System.DateTime from DateTable or DbDataReader.  DATETIME2 is better because
        //          of greater range that can handle all DateTime values.
        static internal SqlDbType InferSqlDbTypeFromType_Katmai(Type type) {
            SqlDbType returnType = InferSqlDbTypeFromType(type);
            if (SqlDbType.DateTime == returnType) {
                returnType = SqlDbType.DateTime2;
            }
            return returnType;
        }
 
        static internal bool IsValidForSmiVersion(SmiExtendedMetaData md, ulong smiVersion) {
            if (SmiContextFactory.LatestVersion == smiVersion) {
                return true;
            }
            else {
                // Yukon doesn't support Structured nor the new time types
                Debug.Assert(SmiContextFactory.YukonVersion == smiVersion, "Other versions should have been eliminated during link stage");
                return md.SqlDbType != SqlDbType.Structured &&
                        md.SqlDbType != SqlDbType.Date &&
                        md.SqlDbType != SqlDbType.DateTime2 &&
                        md.SqlDbType != SqlDbType.DateTimeOffset &&
                        md.SqlDbType != SqlDbType.Time;
            }
        }
 
        static internal SqlMetaData SmiExtendedMetaDataToSqlMetaData(SmiExtendedMetaData source) {
            if (SqlDbType.Xml == source.SqlDbType) {
                return new SqlMetaData(source.Name,
                    source.SqlDbType,
                    source.MaxLength,
                    source.Precision,
                    source.Scale,
                    source.LocaleId,
                    source.CompareOptions,
                    source.TypeSpecificNamePart1,
                    source.TypeSpecificNamePart2,
                    source.TypeSpecificNamePart3,
                    true,
                    source.Type);
            }
 
            return new SqlMetaData(source.Name,
                source.SqlDbType,
                source.MaxLength,
                source.Precision,
                source.Scale,
                source.LocaleId,
                source.CompareOptions,
                source.Type);
        }
 
        // Convert SqlMetaData instance to an SmiExtendedMetaData instance.
 
        internal static SmiExtendedMetaData SqlMetaDataToSmiExtendedMetaData( SqlMetaData source ) {
            // now map everything across to the extended metadata object
            string typeSpecificNamePart1 = null;
            string typeSpecificNamePart2 = null;
            string typeSpecificNamePart3 = null;
            
            if (SqlDbType.Xml == source.SqlDbType) {
                typeSpecificNamePart1 = source.XmlSchemaCollectionDatabase;
                typeSpecificNamePart2 = source.XmlSchemaCollectionOwningSchema;
                typeSpecificNamePart3 = source.XmlSchemaCollectionName;
            }
            else if (SqlDbType.Udt == source.SqlDbType) {
                // Split the input name. UdtTypeName is specified as single 3 part name.
                // NOTE: ParseUdtTypeName throws if format is incorrect
                string typeName = source.ServerTypeName;
                if (null != typeName) {
                    String[] names = SqlParameter.ParseTypeName(typeName, true /* is for UdtTypeName */);
 
                    if (1 == names.Length) {
                        typeSpecificNamePart3 = names[0];
                    }
                    else if (2 == names.Length) {
                        typeSpecificNamePart2 = names[0];
                        typeSpecificNamePart3 = names[1];
                    }
                    else if (3 == names.Length) {
                        typeSpecificNamePart1 = names[0];
                        typeSpecificNamePart2 = names[1];
                        typeSpecificNamePart3 = names[2];
                    }
                    else {
                        throw ADP.ArgumentOutOfRange("typeName");
                    }
 
                    if ((!ADP.IsEmpty(typeSpecificNamePart1) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart1.Length)
                        || (!ADP.IsEmpty(typeSpecificNamePart2) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart2.Length)
                        || (!ADP.IsEmpty(typeSpecificNamePart3) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart3.Length)) {
                        throw ADP.ArgumentOutOfRange("typeName");
                    }
                }
            }                    
 
            return new SmiExtendedMetaData( source.SqlDbType,
                                            source.MaxLength,
                                            source.Precision,
                                            source.Scale,
                                            source.LocaleId,
                                            source.CompareOptions,
                                            source.Type,
                                            source.Name,
                                            typeSpecificNamePart1,
                                            typeSpecificNamePart2,
                                            typeSpecificNamePart3 );
 
 
        }
 
 
        // compare SmiMetaData to SqlMetaData and determine if they are compatible.
        static internal bool IsCompatible(SmiMetaData firstMd, SqlMetaData secondMd) {
            return firstMd.SqlDbType == secondMd.SqlDbType &&
                    firstMd.MaxLength == secondMd.MaxLength &&
                    firstMd.Precision == secondMd.Precision &&
                    firstMd.Scale == secondMd.Scale &&
                    firstMd.CompareOptions == secondMd.CompareOptions &&
                    firstMd.LocaleId == secondMd.LocaleId &&
                    firstMd.Type == secondMd.Type &&
                    firstMd.SqlDbType != SqlDbType.Structured &&  // SqlMetaData doesn't support Structured types
                    !firstMd.IsMultiValued;  // SqlMetaData doesn't have a "multivalued" option
        }
 
        static internal long AdjustMaxLength(SqlDbType dbType, long maxLength) {
            if (SmiMetaData.UnlimitedMaxLengthIndicator != maxLength) {
                if (maxLength < 0) {
                    maxLength = InvalidMaxLength;
                }
 
                switch(dbType) {
                    case SqlDbType.Binary:
                        if (maxLength > SmiMetaData.MaxBinaryLength) {
                            maxLength = InvalidMaxLength;
                        }
                        break;
                    case SqlDbType.Char:
                        if (maxLength > SmiMetaData.MaxANSICharacters) {
                            maxLength = InvalidMaxLength;
                        }
                        break;
                    case SqlDbType.NChar:
                        if (maxLength > SmiMetaData.MaxUnicodeCharacters) {
                            maxLength = InvalidMaxLength;
                        }
                        break;
                    case SqlDbType.NVarChar:
                        // Promote to MAX type if it won't fit in a normal type
                        if (SmiMetaData.MaxUnicodeCharacters < maxLength) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                        break;
                    case SqlDbType.VarBinary:
                        // Promote to MAX type if it won't fit in a normal type
                        if (SmiMetaData.MaxBinaryLength < maxLength) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                        break;
                    case SqlDbType.VarChar:
                        // Promote to MAX type if it won't fit in a normal type
                        if (SmiMetaData.MaxANSICharacters < maxLength) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                        break;
                    default:
                        break;
                }
            }
 
            return maxLength;
        }
 
        // Extract metadata for a single DataColumn
        static internal SmiExtendedMetaData SmiMetaDataFromDataColumn(DataColumn column, DataTable parent) {
            SqlDbType dbType = InferSqlDbTypeFromType_Katmai(column.DataType);
            if (InvalidSqlDbType == dbType) {
                throw SQL.UnsupportedColumnTypeForSqlProvider(column.ColumnName, column.DataType.Name);
            }
 
            long maxLength = AdjustMaxLength(dbType, column.MaxLength);
            if (InvalidMaxLength == maxLength) {
                throw SQL.InvalidColumnMaxLength(column.ColumnName, maxLength);
            }
 
            byte precision;
            byte scale;
            if (column.DataType == typeof(SqlDecimal)) {
 
                // Must scan all values in column to determine best-fit precision & scale
                Debug.Assert(null != parent);
                scale = 0;
                byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
                foreach (DataRow row in parent.Rows) {
                    object obj = row[column];
                    if (!(obj is DBNull)) {
                        SqlDecimal value = (SqlDecimal) obj;
                        if (!value.IsNull) {
                            byte tempNonFractPrec = checked((byte) (value.Precision - value.Scale));
                            if (tempNonFractPrec > nonFractionalPrecision) {
                                nonFractionalPrecision = tempNonFractPrec;
                            }
 
                            if (value.Scale > scale) {
                                scale = value.Scale;
                            }
                        }
                    }
                }
 
                precision = checked((byte)(nonFractionalPrecision + scale));
 
                if (SqlDecimal.MaxPrecision < precision) {
                    throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
                }
                else if (0 == precision) {
                    precision = 1;
                }
            }
            else if (dbType == SqlDbType.DateTime2 || dbType == SqlDbType.DateTimeOffset || dbType == SqlDbType.Time) {
                // Time types care about scale, too.  But have to infer maximums for these.
                precision = 0;
                scale = SmiMetaData.DefaultTime.Scale;
            }
            else if (dbType == SqlDbType.Decimal) {
                // Must scan all values in column to determine best-fit precision & scale
                Debug.Assert(null != parent);
                scale = 0;
                byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
                foreach (DataRow row in parent.Rows) {
                    object obj = row[column];
                    if (!(obj is DBNull)) {
                        SqlDecimal value = (SqlDecimal)(Decimal)obj;
                        byte tempNonFractPrec = checked((byte)(value.Precision - value.Scale));
                        if (tempNonFractPrec > nonFractionalPrecision) {
                            nonFractionalPrecision = tempNonFractPrec;
                        }
 
                        if (value.Scale > scale) {
                            scale = value.Scale;
                        }
                    }
                }
 
                precision = checked((byte)(nonFractionalPrecision + scale));
 
                if (SqlDecimal.MaxPrecision < precision) {
                    throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
                }
                else if (0 == precision) {
                    precision = 1;
                }
            }
            else {
                precision = 0;
                scale = 0;
            }
 
            return new SmiExtendedMetaData(
                                        dbType, 
                                        maxLength, 
                                        precision, 
                                        scale, 
                                        column.Locale.LCID, 
                                        SmiMetaData.DefaultNVarChar.CompareOptions, 
                                        column.DataType, 
                                        false,  // no support for multi-valued columns in a TVP yet
                                        null,   // no support for structured columns yet
                                        null,   // no support for structured columns yet
                                        column.ColumnName, 
                                        null, 
                                        null, 
                                        null);
        }
 
        // Map SmiMetaData from a schema table.
        //  DEVNOTE: since we're using SchemaTable, we can assume that we aren't directly using a SqlDataReader
        //      so we don't support the Sql-specific stuff, like collation
        static internal SmiExtendedMetaData SmiMetaDataFromSchemaTableRow(DataRow schemaRow) {
            // One way or another, we'll need column name, so put it in a local now to shorten code later.
            string colName = "";
            object temp = schemaRow[SchemaTableColumn.ColumnName];
            if (DBNull.Value != temp) {
                colName = (string)temp;
            }
 
            // Determine correct SqlDbType.
            temp = schemaRow[SchemaTableColumn.DataType];
            if (DBNull.Value == temp) {
                throw SQL.NullSchemaTableDataTypeNotSupported(colName);
            }
            Type colType = (Type)temp;
            SqlDbType colDbType = InferSqlDbTypeFromType_Katmai(colType);
            if (InvalidSqlDbType == colDbType) {
                // Unknown through standard mapping, use VarBinary for columns that are Object typed, otherwise error
                if (typeof(object) == colType) {
                    colDbType = SqlDbType.VarBinary;
                }
                else {
                    throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
                }
            }
 
            // Determine metadata modifier values per type (maxlength, precision, scale, etc)
            long maxLength = 0;
            byte precision = 0;
            byte scale = 0;
            switch (colDbType) {
                case SqlDbType.BigInt:
                case SqlDbType.Bit:
                case SqlDbType.DateTime:
                case SqlDbType.Float:
                case SqlDbType.Image:
                case SqlDbType.Int:
                case SqlDbType.Money:
                case SqlDbType.NText:
                case SqlDbType.Real:
                case SqlDbType.UniqueIdentifier:
                case SqlDbType.SmallDateTime:
                case SqlDbType.SmallInt:
                case SqlDbType.SmallMoney:
                case SqlDbType.Text:
                case SqlDbType.Timestamp:
                case SqlDbType.TinyInt:
                case SqlDbType.Variant:
                case SqlDbType.Xml:
                case SqlDbType.Date:
                    // These types require no  metadata modifies
                    break;
                case SqlDbType.Binary:
                case SqlDbType.VarBinary:
                    // These types need a binary max length
                    temp = schemaRow[SchemaTableColumn.ColumnSize];
                    if (DBNull.Value == temp) {
                        // source isn't specifying a size, so assume the worst
                        if (SqlDbType.Binary == colDbType) {
                            maxLength = SmiMetaData.MaxBinaryLength;
                        }
                        else {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                    }
                    else {
                        // We (should) have a valid maxlength, so use it.
                        maxLength = Convert.ToInt64(temp, null);
 
                        // Max length must be 0 to MaxBinaryLength or it can be UnlimitedMAX if type is varbinary
                        //   If it's greater than MaxBinaryLength, just promote it to UnlimitedMAX, if possible
                        if (maxLength > SmiMetaData.MaxBinaryLength) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
 
                        if ((maxLength < 0 &&
                                (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
                                 SqlDbType.Binary == colDbType))) {
                            throw SQL.InvalidColumnMaxLength(colName, maxLength);
                        }
                    }
                    break;
                case SqlDbType.Char:
                case SqlDbType.VarChar:
                    // These types need an ANSI max length
                    temp = schemaRow[SchemaTableColumn.ColumnSize];
                    if (DBNull.Value == temp) {
                        // source isn't specifying a size, so assume the worst
                        if (SqlDbType.Char == colDbType) {
                            maxLength = SmiMetaData.MaxANSICharacters;
                        }
                        else {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                    }
                    else {
                        // We (should) have a valid maxlength, so use it.
                        maxLength = Convert.ToInt64(temp, null);
 
                        // Max length must be 0 to MaxANSICharacters or it can be UnlimitedMAX if type is varbinary
                        //   If it's greater than MaxANSICharacters, just promote it to UnlimitedMAX, if possible
                        if (maxLength > SmiMetaData.MaxANSICharacters) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
 
                        if ((maxLength < 0 && 
                                (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
                                 SqlDbType.Char == colDbType))) {
                            throw SQL.InvalidColumnMaxLength(colName, maxLength);
                        }
                    }
                    break;
                case SqlDbType.NChar:
                case SqlDbType.NVarChar:
                    // These types need a unicode max length
                    temp = schemaRow[SchemaTableColumn.ColumnSize];
                    if (DBNull.Value == temp) {
                        // source isn't specifying a size, so assume the worst
                        if (SqlDbType.NChar == colDbType) {
                            maxLength = SmiMetaData.MaxUnicodeCharacters;
                        }
                        else {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
                    }
                    else {
                        // We (should) have a valid maxlength, so use it.
                        maxLength = Convert.ToInt64(temp, null);
 
                        // Max length must be 0 to MaxUnicodeCharacters or it can be UnlimitedMAX if type is varbinary
                        //   If it's greater than MaxUnicodeCharacters, just promote it to UnlimitedMAX, if possible
                        if (maxLength > SmiMetaData.MaxUnicodeCharacters) {
                            maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
                        }
 
                        if ((maxLength < 0 &&
                                (maxLength != SmiMetaData.UnlimitedMaxLengthIndicator ||
                                 SqlDbType.NChar == colDbType))) {
                            throw SQL.InvalidColumnMaxLength(colName, maxLength);
                        }
                    }
                    break;
                case SqlDbType.Decimal:
                    // Decimal requires precision and scale
                    temp = schemaRow[SchemaTableColumn.NumericPrecision];
                    if (DBNull.Value == temp) {
                        precision = SmiMetaData.DefaultDecimal.Precision;
                    }
                    else {
                        precision = Convert.ToByte(temp, null);
                    }
 
                    temp = schemaRow[SchemaTableColumn.NumericScale];
                    if (DBNull.Value == temp) {
                        scale = SmiMetaData.DefaultDecimal.Scale;
                    }
                    else {
                        scale = Convert.ToByte(temp, null);
                    }
 
                    if (precision < SmiMetaData.MinPrecision || 
                            precision > SqlDecimal.MaxPrecision || 
                            scale < SmiMetaData.MinScale || 
                            scale > SqlDecimal.MaxScale ||
                            scale > precision) {
                        throw SQL.InvalidColumnPrecScale();
                    }
                    break;
                case SqlDbType.Time:
                case SqlDbType.DateTime2:
                case SqlDbType.DateTimeOffset:
                    // requires scale
                    temp = schemaRow[SchemaTableColumn.NumericScale];
                    if (DBNull.Value == temp) {
                        scale = SmiMetaData.DefaultTime.Scale;
                    }
                    else {
                        scale = Convert.ToByte(temp, null);
                    }
 
                    if (scale > SmiMetaData.MaxTimeScale) {
                        throw SQL.InvalidColumnPrecScale();
                    }
                    else if (scale < 0) {
                        scale = SmiMetaData.DefaultTime.Scale;
                    }
                    break;
                case SqlDbType.Udt:
                case SqlDbType.Structured:
                default:
                    // These types are not supported from SchemaTable
                    throw SQL.UnsupportedColumnTypeForSqlProvider(colName, colType.ToString());
            }
 
            return new SmiExtendedMetaData(
                                        colDbType, 
                                        maxLength, 
                                        precision, 
                                        scale, 
                                        System.Globalization.CultureInfo.CurrentCulture.LCID, 
                                        SmiMetaData.GetDefaultForType(colDbType).CompareOptions, 
                                        null,   // no support for UDTs from SchemaTable
                                        false,  // no support for multi-valued columns in a TVP yet
                                        null,   // no support for structured columns yet
                                        null,   // no support for structured columns yet
                                        colName, 
                                        null, 
                                        null, 
                                        null);
        }
    }
}