// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace System.Data.Odbc
{
    internal static class ODBC
    {
        internal static Exception ConnectionClosed()
        {
            return ADP.InvalidOperation(SR.GetString(SR.Odbc_ConnectionClosed));
        }

        internal static Exception OpenConnectionNoOwner()
        {
            return ADP.InvalidOperation(SR.GetString(SR.Odbc_OpenConnectionNoOwner));
        }

        internal static Exception UnknownSQLType(ODBC32.SQL_TYPE sqltype)
        {
            return ADP.Argument(SR.GetString(SR.Odbc_UnknownSQLType, sqltype.ToString()));
        }

        internal static ArgumentException GetSchemaRestrictionRequired()
        {
            return ADP.Argument(SR.GetString(SR.ODBC_GetSchemaRestrictionRequired));
        }

        internal static ArgumentOutOfRangeException NotSupportedEnumerationValue(Type type, int value)
        {
            return ADP.ArgumentOutOfRange(SR.GetString(SR.ODBC_NotSupportedEnumerationValue, type.Name, value.ToString(System.Globalization.CultureInfo.InvariantCulture)), type.Name);
        }

        internal static ArgumentOutOfRangeException NotSupportedCommandType(CommandType value)
        {
#if DEBUG
            switch (value)
            {
                case CommandType.Text:
                case CommandType.StoredProcedure:
                    Debug.Fail("valid CommandType " + value.ToString());
                    break;
                case CommandType.TableDirect:
                    break;
                default:
                    Debug.Fail("invalid CommandType " + value.ToString());
                    break;
            }
#endif
            return ODBC.NotSupportedEnumerationValue(typeof(CommandType), (int)value);
        }
        internal static ArgumentOutOfRangeException NotSupportedIsolationLevel(IsolationLevel value)
        {
#if DEBUG
            switch (value)
            {
                case IsolationLevel.Unspecified:
                case IsolationLevel.ReadUncommitted:
                case IsolationLevel.ReadCommitted:
                case IsolationLevel.RepeatableRead:
                case IsolationLevel.Serializable:
                case IsolationLevel.Snapshot:
                    Debug.Fail("valid IsolationLevel " + value.ToString());
                    break;
                case IsolationLevel.Chaos:
                    break;
                default:
                    Debug.Fail("invalid IsolationLevel " + value.ToString());
                    break;
            }
#endif
            return ODBC.NotSupportedEnumerationValue(typeof(IsolationLevel), (int)value);
        }

        internal static InvalidOperationException NoMappingForSqlTransactionLevel(int value)
        {
            return ADP.DataAdapter(SR.GetString(SR.Odbc_NoMappingForSqlTransactionLevel, value.ToString(CultureInfo.InvariantCulture)));
        }

        internal static Exception NegativeArgument()
        {
            return ADP.Argument(SR.GetString(SR.Odbc_NegativeArgument));
        }
        internal static Exception CantSetPropertyOnOpenConnection()
        {
            return ADP.InvalidOperation(SR.GetString(SR.Odbc_CantSetPropertyOnOpenConnection));
        }
        internal static Exception CantEnableConnectionpooling(ODBC32.SQLRETURN retcode)
        {
            return ADP.DataAdapter(SR.GetString(SR.Odbc_CantEnableConnectionpooling, ODBC32.RetcodeToString(retcode)));
        }
        internal static Exception CantAllocateEnvironmentHandle(ODBC32.SQLRETURN retcode)
        {
            return ADP.DataAdapter(SR.GetString(SR.Odbc_CantAllocateEnvironmentHandle, ODBC32.RetcodeToString(retcode)));
        }
        internal static Exception FailedToGetDescriptorHandle(ODBC32.SQLRETURN retcode)
        {
            return ADP.DataAdapter(SR.GetString(SR.Odbc_FailedToGetDescriptorHandle, ODBC32.RetcodeToString(retcode)));
        }
        internal static Exception NotInTransaction()
        {
            return ADP.InvalidOperation(SR.GetString(SR.Odbc_NotInTransaction));
        }
        internal static Exception UnknownOdbcType(OdbcType odbctype)
        {
            return ADP.InvalidEnumerationValue(typeof(OdbcType), (int)odbctype);
        }
        internal const string Pwd = "pwd";

        [Conditional("DEBUG")]
        internal static void TraceODBC(int level, string method, ODBC32.SQLRETURN retcode)
        {
        }

        internal static short ShortStringLength(string? inputString)
        {
            return checked((short)ADP.StringLength(inputString));
        }
    }

    public static partial class ODBC32
    {
        internal enum SQL_HANDLE : short
        {
            ENV = 1,
            DBC = 2,
            STMT = 3,
            DESC = 4,
        }

        // must be public because it is serialized by OdbcException
        internal enum SQLRETURN : short
        {
            SUCCESS = 0,
            SUCCESS_WITH_INFO = 1,
            ERROR = -1,
            INVALID_HANDLE = -2,
            NO_DATA = 100,
        }

        internal static string RetcodeToString(SQLRETURN retcode)
        {
            switch (retcode)
            {
                case SQLRETURN.SUCCESS: return "SUCCESS";
                case SQLRETURN.SUCCESS_WITH_INFO: return "SUCCESS_WITH_INFO";
                case SQLRETURN.ERROR: return "ERROR";
                case SQLRETURN.INVALID_HANDLE: return "INVALID_HANDLE";
                case SQLRETURN.NO_DATA: return "NO_DATA";
                default:
                    Debug.Fail("Unknown enumerator passed to RetcodeToString method");
                    goto case SQLRETURN.ERROR;
            }
        }



        internal enum SQL_CONVERT : ushort
        {
            BIGINT = 53,
            BINARY = 54,
            BIT = 55,
            CHAR = 56,
            DATE = 57,
            DECIMAL = 58,
            DOUBLE = 59,
            FLOAT = 60,
            INTEGER = 61,
            LONGVARCHAR = 62,
            NUMERIC = 63,
            REAL = 64,
            SMALLINT = 65,
            TIME = 66,
            TIMESTAMP = 67,
            TINYINT = 68,
            VARBINARY = 69,
            VARCHAR = 70,
            LONGVARBINARY = 71,
        }

        [Flags]
        internal enum SQL_CVT
        {
            CHAR = 0x00000001,
            NUMERIC = 0x00000002,
            DECIMAL = 0x00000004,
            INTEGER = 0x00000008,
            SMALLINT = 0x00000010,
            FLOAT = 0x00000020,
            REAL = 0x00000040,
            DOUBLE = 0x00000080,
            VARCHAR = 0x00000100,
            LONGVARCHAR = 0x00000200,
            BINARY = 0x00000400,
            VARBINARY = 0x00000800,
            BIT = 0x00001000,
            TINYINT = 0x00002000,
            BIGINT = 0x00004000,
            DATE = 0x00008000,
            TIME = 0x00010000,
            TIMESTAMP = 0x00020000,
            LONGVARBINARY = 0x00040000,
            INTERVAL_YEAR_MONTH = 0x00080000,
            INTERVAL_DAY_TIME = 0x00100000,
            WCHAR = 0x00200000,
            WLONGVARCHAR = 0x00400000,
            WVARCHAR = 0x00800000,
            GUID = 0x01000000,
        }

        internal enum STMT : short
        {
            CLOSE = 0,
            DROP = 1,
            UNBIND = 2,
            RESET_PARAMS = 3,
        }

        internal enum SQL_MAX
        {
            NUMERIC_LEN = 16,
        }

        internal enum SQL_IS
        {
            POINTER = -4,
            INTEGER = -6,
            UINTEGER = -5,
            SMALLINT = -8,
        }


        //SQL Server specific defines
        //
        internal enum SQL_HC                          // from Odbcss.h
        {
            OFF = 0,                //  FOR BROWSE columns are hidden
            ON = 1,                //  FOR BROWSE columns are exposed
        }

        internal enum SQL_NB                          // from Odbcss.h
        {
            OFF = 0,                //  NO_BROWSETABLE is off
            ON = 1,                //  NO_BROWSETABLE is on
        }

        //  SQLColAttributes driver specific defines.
        //  SQLSet/GetDescField driver specific defines.
        //  Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server driver usage.
        //
        internal enum SQL_CA_SS                       // from Odbcss.h
        {
            BASE = 1200,           // SQL_CA_SS_BASE

            COLUMN_HIDDEN = BASE + 11,      //  Column is hidden (FOR BROWSE)
            COLUMN_KEY = BASE + 12,      //  Column is key column (FOR BROWSE)
            VARIANT_TYPE = BASE + 15,
            VARIANT_SQL_TYPE = BASE + 16,
            VARIANT_SERVER_TYPE = BASE + 17,
        }
        internal enum SQL_SOPT_SS                     // from Odbcss.h
        {
            BASE = 1225,           // SQL_SOPT_SS_BASE
            HIDDEN_COLUMNS = BASE + 2,       // Expose FOR BROWSE hidden columns
            NOBROWSETABLE = BASE + 3,       // Set NOBROWSETABLE option
        }

        internal const short SQL_COMMIT = 0;      //Commit
        internal const short SQL_ROLLBACK = 1;      //Abort

        internal static readonly IntPtr SQL_AUTOCOMMIT_OFF = ADP.PtrZero;
        internal static readonly IntPtr SQL_AUTOCOMMIT_ON = new IntPtr(1);

        internal enum SQL_TRANSACTION
        {
            READ_UNCOMMITTED = 0x00000001,
            READ_COMMITTED = 0x00000002,
            REPEATABLE_READ = 0x00000004,
            SERIALIZABLE = 0x00000008,
            SNAPSHOT = 0x00000020, // VSDD 414121: SQL_TXN_SS_SNAPSHOT == 0x20 (sqlncli.h)
        }

        internal enum SQL_PARAM
        {
            // unused   TYPE_UNKNOWN        =   0,          // SQL_PARAM_TYPE_UNKNOWN
            INPUT = 1,          // SQL_PARAM_INPUT
            INPUT_OUTPUT = 2,          // SQL_PARAM_INPUT_OUTPUT
                                       // unused   RESULT_COL          =   3,          // SQL_RESULT_COL
            OUTPUT = 4,          // SQL_PARAM_OUTPUT
            RETURN_VALUE = 5,          // SQL_RETURN_VALUE
        }

        // SQL_API_* values
        // there are a gillion of these I am only defining the ones currently needed
        // others can be added as needed
        internal enum SQL_API : ushort
        {
            SQLCOLUMNS = 40,
            SQLEXECDIRECT = 11,
            SQLGETTYPEINFO = 47,
            SQLPROCEDURECOLUMNS = 66,
            SQLPROCEDURES = 67,
            SQLSTATISTICS = 53,
            SQLTABLES = 54,
        }


        internal enum SQL_DESC : short
        {
            // from sql.h (ODBCVER >= 3.0)
            //
            COUNT = 1001,
            TYPE = 1002,
            LENGTH = 1003,
            OCTET_LENGTH_PTR = 1004,
            PRECISION = 1005,
            SCALE = 1006,
            DATETIME_INTERVAL_CODE = 1007,
            NULLABLE = 1008,
            INDICATOR_PTR = 1009,
            DATA_PTR = 1010,
            NAME = 1011,
            UNNAMED = 1012,
            OCTET_LENGTH = 1013,
            ALLOC_TYPE = 1099,

            // from sqlext.h (ODBCVER >= 3.0)
            //
            CONCISE_TYPE = SQL_COLUMN.TYPE,
            DISPLAY_SIZE = SQL_COLUMN.DISPLAY_SIZE,
            UNSIGNED = SQL_COLUMN.UNSIGNED,
            UPDATABLE = SQL_COLUMN.UPDATABLE,
            AUTO_UNIQUE_VALUE = SQL_COLUMN.AUTO_INCREMENT,

            TYPE_NAME = SQL_COLUMN.TYPE_NAME,
            TABLE_NAME = SQL_COLUMN.TABLE_NAME,
            SCHEMA_NAME = SQL_COLUMN.OWNER_NAME,
            CATALOG_NAME = SQL_COLUMN.QUALIFIER_NAME,

            BASE_COLUMN_NAME = 22,
            BASE_TABLE_NAME = 23,
        }

        // ODBC version 2.0 style attributes
        // All IdentifierValues are ODBC 1.0 unless marked differently
        //
        internal enum SQL_COLUMN
        {
            COUNT = 0,
            NAME = 1,
            TYPE = 2,
            LENGTH = 3,
            PRECISION = 4,
            SCALE = 5,
            DISPLAY_SIZE = 6,
            NULLABLE = 7,
            UNSIGNED = 8,
            MONEY = 9,
            UPDATABLE = 10,
            AUTO_INCREMENT = 11,
            CASE_SENSITIVE = 12,
            SEARCHABLE = 13,
            TYPE_NAME = 14,
            TABLE_NAME = 15,    // (ODBC 2.0)
            OWNER_NAME = 16,    // (ODBC 2.0)
            QUALIFIER_NAME = 17,    // (ODBC 2.0)
            LABEL = 18,
        }

        internal enum SQL_GROUP_BY
        {
            NOT_SUPPORTED = 0,    // SQL_GB_NOT_SUPPORTED
            GROUP_BY_EQUALS_SELECT = 1,    // SQL_GB_GROUP_BY_EQUALS_SELECT
            GROUP_BY_CONTAINS_SELECT = 2,    // SQL_GB_GROUP_BY_CONTAINS_SELECT
            NO_RELATION = 3,    // SQL_GB_NO_RELATION
            COLLATE = 4,    // SQL_GB_COLLATE - added in ODBC 3.0
        }

        // values from sqlext.h
        internal enum SQL_SQL92_RELATIONAL_JOIN_OPERATORS
        {
            CORRESPONDING_CLAUSE = 0x00000001,    // SQL_SRJO_CORRESPONDING_CLAUSE
            CROSS_JOIN = 0x00000002,    // SQL_SRJO_CROSS_JOIN
            EXCEPT_JOIN = 0x00000004,    // SQL_SRJO_EXCEPT_JOIN
            FULL_OUTER_JOIN = 0x00000008,    // SQL_SRJO_FULL_OUTER_JOIN
            INNER_JOIN = 0x00000010,    // SQL_SRJO_INNER_JOIN
            INTERSECT_JOIN = 0x00000020,    // SQL_SRJO_INTERSECT_JOIN
            LEFT_OUTER_JOIN = 0x00000040,    // SQL_SRJO_LEFT_OUTER_JOIN
            NATURAL_JOIN = 0x00000080,    // SQL_SRJO_NATURAL_JOIN
            RIGHT_OUTER_JOIN = 0x00000100,    // SQL_SRJO_RIGHT_OUTER_JOIN
            UNION_JOIN = 0x00000200,    // SQL_SRJO_UNION_JOIN
        }

        // values from sql.h
        internal enum SQL_OJ_CAPABILITIES
        {
            LEFT = 0x00000001,    // SQL_OJ_LEFT
            RIGHT = 0x00000002,    // SQL_OJ_RIGHT
            FULL = 0x00000004,    // SQL_OJ_FULL
            NESTED = 0x00000008,    // SQL_OJ_NESTED
            NOT_ORDERED = 0x00000010,    // SQL_OJ_NOT_ORDERED
            INNER = 0x00000020,    // SQL_OJ_INNER
            ALL_COMPARISON_OPS = 0x00000040,  //SQL_OJ_ALLCOMPARISON+OPS
        }

        internal enum SQL_UPDATABLE
        {
            READONLY = 0,    // SQL_ATTR_READ_ONLY
            WRITE = 1,    // SQL_ATTR_WRITE
            READWRITE_UNKNOWN = 2,    // SQL_ATTR_READWRITE_UNKNOWN
        }

        internal enum SQL_IDENTIFIER_CASE
        {
            UPPER = 1,    // SQL_IC_UPPER
            LOWER = 2,    // SQL_IC_LOWER
            SENSITIVE = 3,    // SQL_IC_SENSITIVE
            MIXED = 4,    // SQL_IC_MIXED
        }

        // Uniqueness parameter in the SQLStatistics function
        internal enum SQL_INDEX : short
        {
            UNIQUE = 0,
            ALL = 1,
        }

        // Reserved parameter in the SQLStatistics function
        internal enum SQL_STATISTICS_RESERVED : short
        {
            QUICK = 0,                // SQL_QUICK
            ENSURE = 1,                // SQL_ENSURE
        }

        // Identifier type parameter in the SQLSpecialColumns function
        internal enum SQL_SPECIALCOLS : ushort
        {
            BEST_ROWID = 1,            // SQL_BEST_ROWID
            ROWVER = 2,            // SQL_ROWVER
        }

        // Scope parameter in the SQLSpecialColumns function
        internal enum SQL_SCOPE : ushort
        {
            CURROW = 0,            // SQL_SCOPE_CURROW
            TRANSACTION = 1,           // SQL_SCOPE_TRANSACTION
            SESSION = 2,           // SQL_SCOPE_SESSION
        }

        internal enum SQL_NULLABILITY : ushort
        {
            NO_NULLS = 0,                // SQL_NO_NULLS
            NULLABLE = 1,                // SQL_NULLABLE
            UNKNOWN = 2,                // SQL_NULLABLE_UNKNOWN
        }

        internal enum SQL_SEARCHABLE
        {
            UNSEARCHABLE = 0,        // SQL_UNSEARCHABLE
            LIKE_ONLY = 1,        // SQL_LIKE_ONLY
            ALL_EXCEPT_LIKE = 2,        // SQL_ALL_EXCEPT_LIKE
            SEARCHABLE = 3,        // SQL_SEARCHABLE
        }

        internal enum SQL_UNNAMED
        {
            NAMED = 0,                   // SQL_NAMED
            UNNAMED = 1,                 // SQL_UNNAMED
        }
        // todo:move
        // internal constants
        // not odbc specific
        //
        internal enum HANDLER
        {
            IGNORE = 0x00000000,
            THROW = 0x00000001,
        }

        // values for SQLStatistics TYPE column
        internal enum SQL_STATISTICSTYPE
        {
            TABLE_STAT = 0,                    // TABLE Statistics
            INDEX_CLUSTERED = 1,                    // CLUSTERED index statistics
            INDEX_HASHED = 2,                    // HASHED index statistics
            INDEX_OTHER = 3,                    // OTHER index statistics
        }

        // values for SQLProcedures PROCEDURE_TYPE column
        internal enum SQL_PROCEDURETYPE
        {
            UNKNOWN = 0,                    // procedure is of unknow type
            PROCEDURE = 1,                    // procedure is a procedure
            FUNCTION = 2,                    // procedure is a function
        }

        // private constants
        // to define data types (see below)
        //
        private const int SIGNED_OFFSET = -20;    // SQL_SIGNED_OFFSET
        private const int UNSIGNED_OFFSET = -22;    // SQL_UNSIGNED_OFFSET

        //C Data Types - used when getting data (SQLGetData)
        internal enum SQL_C : short
        {
            CHAR = 1,                     //SQL_C_CHAR
            WCHAR = -8,                     //SQL_C_WCHAR
            SLONG = 4 + SIGNED_OFFSET,     //SQL_C_LONG+SQL_SIGNED_OFFSET
                                           //          ULONG           =    4 + UNSIGNED_OFFSET,   //SQL_C_LONG+SQL_UNSIGNED_OFFSET
            SSHORT = 5 + SIGNED_OFFSET,     //SQL_C_SSHORT+SQL_SIGNED_OFFSET
                                            //          USHORT          =    5 + UNSIGNED_OFFSET,   //SQL_C_USHORT+SQL_UNSIGNED_OFFSET
            REAL = 7,                     //SQL_C_REAL
            DOUBLE = 8,                     //SQL_C_DOUBLE
            BIT = -7,                     //SQL_C_BIT
                                          //          STINYINT        =   -6 + SIGNED_OFFSET,     //SQL_C_STINYINT+SQL_SIGNED_OFFSET
            UTINYINT = -6 + UNSIGNED_OFFSET,   //SQL_C_UTINYINT+SQL_UNSIGNED_OFFSET
            SBIGINT = -5 + SIGNED_OFFSET,     //SQL_C_SBIGINT+SQL_SIGNED_OFFSET
            UBIGINT = -5 + UNSIGNED_OFFSET,   //SQL_C_UBIGINT+SQL_UNSIGNED_OFFSET
            BINARY = -2,                     //SQL_C_BINARY
            TIMESTAMP = 11,                     //SQL_C_TIMESTAMP

            TYPE_DATE = 91,                     //SQL_C_TYPE_DATE
            TYPE_TIME = 92,                     //SQL_C_TYPE_TIME
            TYPE_TIMESTAMP = 93,                     //SQL_C_TYPE_TIMESTAMP

            NUMERIC = 2,                     //SQL_C_NUMERIC
            GUID = -11,                    //SQL_C_GUID
            DEFAULT = 99,                     //SQL_C_DEFAULT
            ARD_TYPE = -99,                    //SQL_ARD_TYPE
        }

        //SQL Data Types - returned as column types (SQLColAttribute)
        internal enum SQL_TYPE : short
        {
            CHAR = SQL_C.CHAR,             //SQL_CHAR
            VARCHAR = 12,                     //SQL_VARCHAR
            LONGVARCHAR = -1,                     //SQL_LONGVARCHAR
            WCHAR = SQL_C.WCHAR,            //SQL_WCHAR
            WVARCHAR = -9,                     //SQL_WVARCHAR
            WLONGVARCHAR = -10,                    //SQL_WLONGVARCHAR
            DECIMAL = 3,                      //SQL_DECIMAL
            NUMERIC = SQL_C.NUMERIC,          //SQL_NUMERIC
            SMALLINT = 5,                      //SQL_SMALLINT
            INTEGER = 4,                      //SQL_INTEGER
            REAL = SQL_C.REAL,             //SQL_REAL
            FLOAT = 6,                      //SQL_FLOAT
            DOUBLE = SQL_C.DOUBLE,           //SQL_DOUBLE
            BIT = SQL_C.BIT,              //SQL_BIT
            TINYINT = -6,                     //SQL_TINYINT
            BIGINT = -5,                     //SQL_BIGINT
            BINARY = SQL_C.BINARY,           //SQL_BINARY
            VARBINARY = -3,                     //SQL_VARBINARY
            LONGVARBINARY = -4,                     //SQL_LONGVARBINARY

            //          DATE            =   9,                      //SQL_DATE
            TYPE_DATE = SQL_C.TYPE_DATE,        //SQL_TYPE_DATE
            TYPE_TIME = SQL_C.TYPE_TIME,        //SQL_TYPE_TIME
            TIMESTAMP = SQL_C.TIMESTAMP,        //SQL_TIMESTAMP
            TYPE_TIMESTAMP = SQL_C.TYPE_TIMESTAMP,   //SQL_TYPE_TIMESTAMP


            GUID = SQL_C.GUID,             //SQL_GUID

            //  from odbcss.h in mdac 9.0 sources!
            //  Driver specific SQL type defines.
            //  Microsoft has -150 thru -199 reserved for Microsoft SQL Server driver usage.
            //
            SS_VARIANT = -150,
            SS_UDT = -151,
            SS_XML = -152,
            SS_UTCDATETIME = -153,
            SS_TIME_EX = -154,
        }

        internal const short SQL_ALL_TYPES = 0;
        internal static readonly IntPtr SQL_HANDLE_NULL = ADP.PtrZero;
        internal const int SQL_NULL_DATA = -1;   // sql.h
        internal const int SQL_NO_TOTAL = -4;   // sqlext.h

        internal const int SQL_DEFAULT_PARAM = -5;
        //      internal const Int32  SQL_IGNORE         = -6;

        // column ordinals for SQLProcedureColumns result set
        // this column ordinals are not defined in any c/c++ header but in the ODBC Programmer's Reference under SQLProcedureColumns
        //
        internal const int COLUMN_NAME = 4;
        internal const int COLUMN_TYPE = 5;
        internal const int DATA_TYPE = 6;
        internal const int COLUMN_SIZE = 8;
        internal const int DECIMAL_DIGITS = 10;
        internal const int NUM_PREC_RADIX = 11;

        internal enum SQL_ATTR
        {
            APP_ROW_DESC = 10010,              // (ODBC 3.0)
            APP_PARAM_DESC = 10011,              // (ODBC 3.0)
            IMP_ROW_DESC = 10012,              // (ODBC 3.0)
            IMP_PARAM_DESC = 10013,              // (ODBC 3.0)
            METADATA_ID = 10014,              // (ODBC 3.0)
            ODBC_VERSION = 200,
            CONNECTION_POOLING = 201,
            AUTOCOMMIT = 102,
            TXN_ISOLATION = 108,
            CURRENT_CATALOG = 109,
            LOGIN_TIMEOUT = 103,
            QUERY_TIMEOUT = 0,                  // from sqlext.h
            CONNECTION_DEAD = 1209,               // from sqlext.h

            // from sqlncli.h
            SQL_COPT_SS_BASE = 1200,
            SQL_COPT_SS_ENLIST_IN_DTC = (SQL_COPT_SS_BASE + 7),
            SQL_COPT_SS_TXN_ISOLATION = (SQL_COPT_SS_BASE + 27), // Used to set/get any driver-specific or ODBC-defined TXN iso level
        }

        //SQLGetInfo
        internal enum SQL_INFO : ushort
        {
            DATA_SOURCE_NAME = 2,    // SQL_DATA_SOURCE_NAME in sql.h
            SERVER_NAME = 13,   // SQL_SERVER_NAME in sql.h
            DRIVER_NAME = 6,    // SQL_DRIVER_NAME as defined in sqlext.h
            DRIVER_VER = 7,    // SQL_DRIVER_VER as defined in sqlext.h
            ODBC_VER = 10,   // SQL_ODBC_VER as defined in sqlext.h
            SEARCH_PATTERN_ESCAPE = 14,   // SQL_SEARCH_PATTERN_ESCAPE from sql.h
            DBMS_VER = 18,
            DBMS_NAME = 17,   // SQL_DBMS_NAME as defined in sqlext.h
            IDENTIFIER_CASE = 28,   // SQL_IDENTIFIER_CASE from sql.h
            IDENTIFIER_QUOTE_CHAR = 29,   // SQL_IDENTIFIER_QUOTE_CHAR from sql.h
            CATALOG_NAME_SEPARATOR = 41,   // SQL_CATALOG_NAME_SEPARATOR
            DRIVER_ODBC_VER = 77,   // SQL_DRIVER_ODBC_VER as defined in sqlext.h
            GROUP_BY = 88,   // SQL_GROUP_BY as defined in  sqlext.h
            KEYWORDS = 89,   // SQL_KEYWORDS as defined in sqlext.h
            ORDER_BY_COLUMNS_IN_SELECT = 90,   // SQL_ORDER_BY_COLUNS_IN_SELECT in sql.h
            QUOTED_IDENTIFIER_CASE = 93,   // SQL_QUOTED_IDENTIFIER_CASE in sqlext.h
            SQL_OJ_CAPABILITIES_30 = 115, //SQL_OJ_CAPABILITIES from sql.h
            SQL_OJ_CAPABILITIES_20 = 65003, //SQL_OJ_CAPABILITIES from sqlext.h
            SQL_SQL92_RELATIONAL_JOIN_OPERATORS = 161, //SQL_SQL92_RELATIONAL_JOIN_OPERATORS from sqlext.h
        }

        internal static readonly IntPtr SQL_OV_ODBC3 = new IntPtr(3);
        internal const int SQL_NTS = -3;       //flags for null-terminated string

        //Pooling
        internal static readonly IntPtr SQL_CP_OFF = new IntPtr(0);       //Connection Pooling disabled
        internal static readonly IntPtr SQL_CP_ONE_PER_DRIVER = new IntPtr(1);       //One pool per driver
        internal static readonly IntPtr SQL_CP_ONE_PER_HENV = new IntPtr(2);       //One pool per environment

        /* values for SQL_ATTR_CONNECTION_DEAD */
        internal const int SQL_CD_TRUE = 1;
        internal const int SQL_CD_FALSE = 0;

        internal const int SQL_DTC_DONE = 0;
        internal const int SQL_IS_POINTER = -4;
        internal const int SQL_IS_PTR = 1;

        internal enum SQL_DRIVER
        {
            NOPROMPT = 0,
            COMPLETE = 1,
            PROMPT = 2,
            COMPLETE_REQUIRED = 3,
        }

        // Column set for SQLPrimaryKeys
        internal enum SQL_PRIMARYKEYS : short
        {
            // CATALOGNAME = 1,                // TABLE_CAT
            // SCHEMANAME = 2,                 // TABLE_SCHEM
            // TABLENAME = 3,                  // TABLE_NAME
            COLUMNNAME = 4,                    // COLUMN_NAME
            // KEY_SEQ = 5,                    // KEY_SEQ
            // PKNAME = 6,                     // PK_NAME
        }

        // Column set for SQLStatistics
        internal enum SQL_STATISTICS : short
        {
            // CATALOGNAME = 1,               // TABLE_CAT
            // SCHEMANAME = 2,                // TABLE_SCHEM
            // TABLENAME = 3,                 // TABLE_NAME
            // NONUNIQUE = 4,                 // NON_UNIQUE
            // INDEXQUALIFIER = 5,            // INDEX_QUALIFIER
            INDEXNAME = 6,                    // INDEX_NAME
            // TYPE = 7,                      // TYPE
            ORDINAL_POSITION = 8,             // ORDINAL_POSITION
            COLUMN_NAME = 9,                  // COLUMN_NAME
            // ASC_OR_DESC = 10,              // ASC_OR_DESC
            // CARDINALITY = 11,              // CARDINALITY
            // PAGES = 12,                    // PAGES
            // FILTER_CONDITION = 13,         // FILTER_CONDITION
        }

        // Column set for SQLSpecialColumns
        internal enum SQL_SPECIALCOLUMNSET : short
        {
            // SCOPE = 1,                       // SCOPE
            COLUMN_NAME = 2,                    // COLUMN_NAME
            // DATA_TYPE           = 3,         // DATA_TYPE
            // TYPE_NAME           = 4,         // TYPE_NAME
            // COLUMN_SIZE         = 5,         // COLUMN_SIZE
            // BUFFER_LENGTH       = 6,         // BUFFER_LENGTH
            // DECIMAL_DIGITS      = 7,         // DECIMAL_DIGITS
            // PSEUDO_COLUMN       = 8,         // PSEUDO_COLUMN
        }

        internal const short SQL_DIAG_SQLSTATE = 4;
        internal const short SQL_RESULT_COL = 3;

        // Helpers
        internal static OdbcErrorCollection GetDiagErrors(string? source, OdbcHandle hrHandle, SQLRETURN retcode)
        {
            OdbcErrorCollection errors = new OdbcErrorCollection();
            GetDiagErrors(errors, source, hrHandle, retcode);
            return errors;
        }

        internal static void GetDiagErrors(OdbcErrorCollection errors, string? source, OdbcHandle hrHandle, SQLRETURN retcode)
        {
            Debug.Assert(retcode != ODBC32.SQLRETURN.INVALID_HANDLE, "retcode must never be ODBC32.RetCode.INVALID_HANDLE");
            if (SQLRETURN.SUCCESS != retcode)
            {
                int NativeError;
                short iRec = 0;
                short cchActual;

                StringBuilder message = new StringBuilder(1024);
                string sqlState;
                bool moreerrors = true;
                while (moreerrors)
                {
                    ++iRec;

                    retcode = hrHandle.GetDiagnosticRecord(iRec, out sqlState, message, out NativeError, out cchActual);
                    if ((SQLRETURN.SUCCESS_WITH_INFO == retcode) && (message.Capacity - 1 < cchActual))
                    {
                        message.Capacity = cchActual + 1;
                        retcode = hrHandle.GetDiagnosticRecord(iRec, out sqlState, message, out NativeError, out _);
                    }

                    //Note: SUCCESS_WITH_INFO from SQLGetDiagRec would be because
                    //the buffer is not large enough for the error string.
                    moreerrors = (retcode == SQLRETURN.SUCCESS || retcode == SQLRETURN.SUCCESS_WITH_INFO);
                    if (moreerrors)
                    {
                        //Sets up the InnerException as well...
                        errors.Add(new OdbcError(
                            source,
                            message.ToString(),
                            sqlState,
                            NativeError
                            )
                        );
                    }
                }
            }
        }
    }

    internal sealed class TypeMap
    { // MDAC 68988
      //      private TypeMap                                           (OdbcType odbcType,         DbType dbType,                Type type,        ODBC32.SQL_TYPE sql_type,       ODBC32.SQL_C sql_c,          ODBC32.SQL_C param_sql_c,   int bsize, int csize, bool signType)
      //      ---------------                                            ------------------         --------------                ----------        -------------------------       -------------------          -------------------------   -----------------------
        private static readonly TypeMap s_bigInt = new TypeMap(OdbcType.BigInt, DbType.Int64, typeof(long), ODBC32.SQL_TYPE.BIGINT, ODBC32.SQL_C.SBIGINT, ODBC32.SQL_C.SBIGINT, 8, 20, true);
        private static readonly TypeMap s_binary = new TypeMap(OdbcType.Binary, DbType.Binary, typeof(byte[]), ODBC32.SQL_TYPE.BINARY, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        private static readonly TypeMap s_bit = new TypeMap(OdbcType.Bit, DbType.Boolean, typeof(bool), ODBC32.SQL_TYPE.BIT, ODBC32.SQL_C.BIT, ODBC32.SQL_C.BIT, 1, 1, false);
        internal static readonly TypeMap _Char = new TypeMap(OdbcType.Char, DbType.AnsiStringFixedLength, typeof(string), ODBC32.SQL_TYPE.CHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.CHAR, -1, -1, false);
        private static readonly TypeMap s_dateTime = new TypeMap(OdbcType.DateTime, DbType.DateTime, typeof(DateTime), ODBC32.SQL_TYPE.TYPE_TIMESTAMP, ODBC32.SQL_C.TYPE_TIMESTAMP, ODBC32.SQL_C.TYPE_TIMESTAMP, 16, 23, false);
        private static readonly TypeMap s_date = new TypeMap(OdbcType.Date, DbType.Date, typeof(DateTime), ODBC32.SQL_TYPE.TYPE_DATE, ODBC32.SQL_C.TYPE_DATE, ODBC32.SQL_C.TYPE_DATE, 6, 10, false);
        private static readonly TypeMap s_time = new TypeMap(OdbcType.Time, DbType.Time, typeof(TimeSpan), ODBC32.SQL_TYPE.TYPE_TIME, ODBC32.SQL_C.TYPE_TIME, ODBC32.SQL_C.TYPE_TIME, 6, 12, false);
        private static readonly TypeMap s_decimal = new TypeMap(OdbcType.Decimal, DbType.Decimal, typeof(decimal), ODBC32.SQL_TYPE.DECIMAL, ODBC32.SQL_C.NUMERIC, ODBC32.SQL_C.NUMERIC, 19, ADP.DecimalMaxPrecision28, false);
        //        private static  readonly TypeMap _Currency   = new TypeMap(OdbcType.Decimal,          DbType.Currency,              typeof(Decimal),  ODBC32.SQL_TYPE.DECIMAL,        ODBC32.SQL_C.NUMERIC,        ODBC32.SQL_C.NUMERIC,        19, ADP.DecimalMaxPrecision28, false);
        private static readonly TypeMap s_double = new TypeMap(OdbcType.Double, DbType.Double, typeof(double), ODBC32.SQL_TYPE.DOUBLE, ODBC32.SQL_C.DOUBLE, ODBC32.SQL_C.DOUBLE, 8, 15, false);
        internal static readonly TypeMap _Image = new TypeMap(OdbcType.Image, DbType.Binary, typeof(byte[]), ODBC32.SQL_TYPE.LONGVARBINARY, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        private static readonly TypeMap s_int = new TypeMap(OdbcType.Int, DbType.Int32, typeof(int), ODBC32.SQL_TYPE.INTEGER, ODBC32.SQL_C.SLONG, ODBC32.SQL_C.SLONG, 4, 10, true);
        private static readonly TypeMap s_NChar = new TypeMap(OdbcType.NChar, DbType.StringFixedLength, typeof(string), ODBC32.SQL_TYPE.WCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.WCHAR, -1, -1, false);
        internal static readonly TypeMap _NText = new TypeMap(OdbcType.NText, DbType.String, typeof(string), ODBC32.SQL_TYPE.WLONGVARCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.WCHAR, -1, -1, false);
        private static readonly TypeMap s_numeric = new TypeMap(OdbcType.Numeric, DbType.Decimal, typeof(decimal), ODBC32.SQL_TYPE.NUMERIC, ODBC32.SQL_C.NUMERIC, ODBC32.SQL_C.NUMERIC, 19, ADP.DecimalMaxPrecision28, false);
        internal static readonly TypeMap _NVarChar = new TypeMap(OdbcType.NVarChar, DbType.String, typeof(string), ODBC32.SQL_TYPE.WVARCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.WCHAR, -1, -1, false);
        private static readonly TypeMap s_real = new TypeMap(OdbcType.Real, DbType.Single, typeof(float), ODBC32.SQL_TYPE.REAL, ODBC32.SQL_C.REAL, ODBC32.SQL_C.REAL, 4, 7, false);
        private static readonly TypeMap s_uniqueId = new TypeMap(OdbcType.UniqueIdentifier, DbType.Guid, typeof(Guid), ODBC32.SQL_TYPE.GUID, ODBC32.SQL_C.GUID, ODBC32.SQL_C.GUID, 16, 36, false);
        private static readonly TypeMap s_smallDT = new TypeMap(OdbcType.SmallDateTime, DbType.DateTime, typeof(DateTime), ODBC32.SQL_TYPE.TYPE_TIMESTAMP, ODBC32.SQL_C.TYPE_TIMESTAMP, ODBC32.SQL_C.TYPE_TIMESTAMP, 16, 23, false);
        private static readonly TypeMap s_smallInt = new TypeMap(OdbcType.SmallInt, DbType.Int16, typeof(short), ODBC32.SQL_TYPE.SMALLINT, ODBC32.SQL_C.SSHORT, ODBC32.SQL_C.SSHORT, 2, 5, true);
        internal static readonly TypeMap _Text = new TypeMap(OdbcType.Text, DbType.AnsiString, typeof(string), ODBC32.SQL_TYPE.LONGVARCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.CHAR, -1, -1, false);
        private static readonly TypeMap s_timestamp = new TypeMap(OdbcType.Timestamp, DbType.Binary, typeof(byte[]), ODBC32.SQL_TYPE.BINARY, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        private static readonly TypeMap s_tinyInt = new TypeMap(OdbcType.TinyInt, DbType.Byte, typeof(byte), ODBC32.SQL_TYPE.TINYINT, ODBC32.SQL_C.UTINYINT, ODBC32.SQL_C.UTINYINT, 1, 3, true);
        private static readonly TypeMap s_varBinary = new TypeMap(OdbcType.VarBinary, DbType.Binary, typeof(byte[]), ODBC32.SQL_TYPE.VARBINARY, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        internal static readonly TypeMap _VarChar = new TypeMap(OdbcType.VarChar, DbType.AnsiString, typeof(string), ODBC32.SQL_TYPE.VARCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.CHAR, -1, -1, false);
        private static readonly TypeMap s_variant = new TypeMap(OdbcType.Binary, DbType.Binary, typeof(object), ODBC32.SQL_TYPE.SS_VARIANT, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        private static readonly TypeMap s_UDT = new TypeMap(OdbcType.Binary, DbType.Binary, typeof(object), ODBC32.SQL_TYPE.SS_UDT, ODBC32.SQL_C.BINARY, ODBC32.SQL_C.BINARY, -1, -1, false);
        private static readonly TypeMap s_XML = new TypeMap(OdbcType.Text, DbType.AnsiString, typeof(string), ODBC32.SQL_TYPE.LONGVARCHAR, ODBC32.SQL_C.WCHAR, ODBC32.SQL_C.CHAR, -1, -1, false);

        internal readonly OdbcType _odbcType;
        internal readonly DbType _dbType;
        internal readonly Type _type;

        internal readonly ODBC32.SQL_TYPE _sql_type;
        internal readonly ODBC32.SQL_C _sql_c;
        internal readonly ODBC32.SQL_C _param_sql_c;


        internal readonly int _bufferSize;  // fixed length byte size to reserve for buffer
        internal readonly int _columnSize;  // column size passed to SQLBindParameter
        internal readonly bool _signType;   // this type may be has signature information

        private TypeMap(OdbcType odbcType, DbType dbType, Type type, ODBC32.SQL_TYPE sql_type, ODBC32.SQL_C sql_c, ODBC32.SQL_C param_sql_c, int bsize, int csize, bool signType)
        {
            _odbcType = odbcType;
            _dbType = dbType;
            _type = type;

            _sql_type = sql_type;
            _sql_c = sql_c;
            _param_sql_c = param_sql_c; // alternative sql_c type for parameters

            _bufferSize = bsize;
            _columnSize = csize;
            _signType = signType;
        }

        internal static TypeMap FromOdbcType(OdbcType odbcType) =>
            odbcType switch
            {
                OdbcType.BigInt => s_bigInt,
                OdbcType.Binary => s_binary,
                OdbcType.Bit => s_bit,
                OdbcType.Char => _Char,
                OdbcType.DateTime => s_dateTime,
                OdbcType.Date => s_date,
                OdbcType.Time => s_time,
                OdbcType.Double => s_double,
                OdbcType.Decimal => s_decimal,
                OdbcType.Image => _Image,
                OdbcType.Int => s_int,
                OdbcType.NChar => s_NChar,
                OdbcType.NText => _NText,
                OdbcType.Numeric => s_numeric,
                OdbcType.NVarChar => _NVarChar,
                OdbcType.Real => s_real,
                OdbcType.UniqueIdentifier => s_uniqueId,
                OdbcType.SmallDateTime => s_smallDT,
                OdbcType.SmallInt => s_smallInt,
                OdbcType.Text => _Text,
                OdbcType.Timestamp => s_timestamp,
                OdbcType.TinyInt => s_tinyInt,
                OdbcType.VarBinary => s_varBinary,
                OdbcType.VarChar => _VarChar,
                _ => throw ODBC.UnknownOdbcType(odbcType),
            };

        internal static TypeMap FromDbType(DbType dbType)
        {
            switch (dbType)
            {
                case DbType.AnsiString: return _VarChar;
                case DbType.AnsiStringFixedLength: return _Char;
                case DbType.Binary: return s_varBinary;
                case DbType.Byte: return s_tinyInt;
                case DbType.Boolean: return s_bit;
                case DbType.Currency: return s_decimal;
                //            case DbType.Currency:   return _Currency;
                case DbType.Date: return s_date;
                case DbType.Time: return s_time;
                case DbType.DateTime: return s_dateTime;
                case DbType.Decimal: return s_decimal;
                case DbType.Double: return s_double;
                case DbType.Guid: return s_uniqueId;
                case DbType.Int16: return s_smallInt;
                case DbType.Int32: return s_int;
                case DbType.Int64: return s_bigInt;
                case DbType.Single: return s_real;
                case DbType.String: return _NVarChar;
                case DbType.StringFixedLength: return s_NChar;
                case DbType.Object:
                case DbType.SByte:
                case DbType.UInt16:
                case DbType.UInt32:
                case DbType.UInt64:
                case DbType.VarNumeric:
                default: throw ADP.DbTypeNotSupported(dbType, typeof(OdbcType));
            }
        }

        internal static TypeMap FromSystemType(Type dataType)
        {
            switch (Type.GetTypeCode(dataType))
            {
                case TypeCode.Empty: throw ADP.InvalidDataType(TypeCode.Empty);
                case TypeCode.Object:
                    if (dataType == typeof(byte[]))
                    {
                        return s_varBinary;
                    }
                    else if (dataType == typeof(System.Guid))
                    {
                        return s_uniqueId;
                    }
                    else if (dataType == typeof(System.TimeSpan))
                    {
                        return s_time;
                    }
                    else if (dataType == typeof(char[]))
                    {
                        return _NVarChar;
                    }
                    throw ADP.UnknownDataType(dataType);

                case TypeCode.DBNull: throw ADP.InvalidDataType(TypeCode.DBNull);
                case TypeCode.Boolean: return s_bit;

                // devnote: Char is actually not supported. Our _Char type is actually a fixed length string, not a single character
                //            case TypeCode.Char:      return _Char;
                case TypeCode.SByte: return s_smallInt;
                case TypeCode.Byte: return s_tinyInt;
                case TypeCode.Int16: return s_smallInt;
                case TypeCode.UInt16: return s_int;
                case TypeCode.Int32: return s_int;
                case TypeCode.UInt32: return s_bigInt;
                case TypeCode.Int64: return s_bigInt;
                case TypeCode.UInt64: return s_numeric;
                case TypeCode.Single: return s_real;
                case TypeCode.Double: return s_double;
                case TypeCode.Decimal: return s_numeric;
                case TypeCode.DateTime: return s_dateTime;

                case TypeCode.Char:
                case TypeCode.String: return _NVarChar;

                default: throw ADP.UnknownDataTypeCode(dataType, Type.GetTypeCode(dataType));
            }
        }

        internal static TypeMap FromSqlType(ODBC32.SQL_TYPE sqltype)
        {
            switch (sqltype)
            {
                case ODBC32.SQL_TYPE.CHAR: return _Char;
                case ODBC32.SQL_TYPE.VARCHAR: return _VarChar;
                case ODBC32.SQL_TYPE.LONGVARCHAR: return _Text;
                case ODBC32.SQL_TYPE.WCHAR: return s_NChar;
                case ODBC32.SQL_TYPE.WVARCHAR: return _NVarChar;
                case ODBC32.SQL_TYPE.WLONGVARCHAR: return _NText;
                case ODBC32.SQL_TYPE.DECIMAL: return s_decimal;
                case ODBC32.SQL_TYPE.NUMERIC: return s_numeric;
                case ODBC32.SQL_TYPE.SMALLINT: return s_smallInt;
                case ODBC32.SQL_TYPE.INTEGER: return s_int;
                case ODBC32.SQL_TYPE.REAL: return s_real;
                case ODBC32.SQL_TYPE.FLOAT: return s_double;
                case ODBC32.SQL_TYPE.DOUBLE: return s_double;
                case ODBC32.SQL_TYPE.BIT: return s_bit;
                case ODBC32.SQL_TYPE.TINYINT: return s_tinyInt;
                case ODBC32.SQL_TYPE.BIGINT: return s_bigInt;
                case ODBC32.SQL_TYPE.BINARY: return s_binary;
                case ODBC32.SQL_TYPE.VARBINARY: return s_varBinary;
                case ODBC32.SQL_TYPE.LONGVARBINARY: return _Image;
                case ODBC32.SQL_TYPE.TYPE_DATE: return s_date;
                case ODBC32.SQL_TYPE.TYPE_TIME: return s_time;
                case ODBC32.SQL_TYPE.TIMESTAMP:
                case ODBC32.SQL_TYPE.TYPE_TIMESTAMP: return s_dateTime;
                case ODBC32.SQL_TYPE.GUID: return s_uniqueId;
                case ODBC32.SQL_TYPE.SS_VARIANT: return s_variant;
                case ODBC32.SQL_TYPE.SS_UDT: return s_UDT;
                case ODBC32.SQL_TYPE.SS_XML: return s_XML;

                case ODBC32.SQL_TYPE.SS_UTCDATETIME:
                case ODBC32.SQL_TYPE.SS_TIME_EX:
                    throw ODBC.UnknownSQLType(sqltype);
                default:
                    throw ODBC.UnknownSQLType(sqltype);
            }
        }

        // Upgrade integer datatypes to missinterpretaion of the highest bit
        // (e.g. 0xff could be 255 if unsigned but is -1 if signed)
        //
        internal static TypeMap UpgradeSignedType(TypeMap typeMap, bool unsigned)
        {
            // upgrade unsigned types to be able to hold data that has the highest bit set
            //
            if (unsigned)
            {
                return typeMap._dbType switch
                {
                    DbType.Int64 => s_decimal,        // upgrade to decimal
                    DbType.Int32 => s_bigInt,         // upgrade to 64 bit
                    DbType.Int16 => s_int,            // upgrade to 32 bit
                    _ => typeMap,
                };
            }
            else
            {
                return typeMap._dbType switch
                {
                    DbType.Byte => s_smallInt,       // upgrade to 16 bit
                    _ => typeMap,
                };
            }
        } // end UpgradeSignedType
    }
}
