/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "todbc_util.h"
#include "todbc_log.h"

#include <iconv.h>
#include <sqlext.h>


#define SQL_CASE(type) case type: return #type

const char* sql_sql_type(int type) {
  switch (type) {
    SQL_CASE(SQL_BIT);
    SQL_CASE(SQL_TINYINT);
    SQL_CASE(SQL_SMALLINT);
    SQL_CASE(SQL_INTEGER);
    SQL_CASE(SQL_BIGINT);
    SQL_CASE(SQL_FLOAT);
    SQL_CASE(SQL_DOUBLE);
    SQL_CASE(SQL_DECIMAL);
    SQL_CASE(SQL_NUMERIC);
    SQL_CASE(SQL_REAL);
    SQL_CASE(SQL_CHAR);
    SQL_CASE(SQL_VARCHAR);
    SQL_CASE(SQL_LONGVARCHAR);
    SQL_CASE(SQL_WCHAR);
    SQL_CASE(SQL_WVARCHAR);
    SQL_CASE(SQL_WLONGVARCHAR);
    SQL_CASE(SQL_BINARY);
    SQL_CASE(SQL_VARBINARY);
    SQL_CASE(SQL_LONGVARBINARY);
    SQL_CASE(SQL_DATE);
    SQL_CASE(SQL_TIME);
    SQL_CASE(SQL_TIMESTAMP);
    SQL_CASE(SQL_TYPE_DATE);
    SQL_CASE(SQL_TYPE_TIME);
    SQL_CASE(SQL_TYPE_TIMESTAMP);
    SQL_CASE(SQL_INTERVAL_MONTH);
    SQL_CASE(SQL_INTERVAL_YEAR);
    SQL_CASE(SQL_INTERVAL_YEAR_TO_MONTH);
    SQL_CASE(SQL_INTERVAL_DAY);
    SQL_CASE(SQL_INTERVAL_HOUR);
    SQL_CASE(SQL_INTERVAL_MINUTE);
    SQL_CASE(SQL_INTERVAL_SECOND);
    SQL_CASE(SQL_INTERVAL_DAY_TO_HOUR);
    SQL_CASE(SQL_INTERVAL_DAY_TO_MINUTE);
    SQL_CASE(SQL_INTERVAL_DAY_TO_SECOND);
    SQL_CASE(SQL_INTERVAL_HOUR_TO_MINUTE);
    SQL_CASE(SQL_INTERVAL_HOUR_TO_SECOND);
    SQL_CASE(SQL_INTERVAL_MINUTE_TO_SECOND);
    SQL_CASE(SQL_GUID);
    SQL_CASE(SQL_ALL_TYPES);
    default: return "UNKNOWN";
  }
}

const char* sql_c_type(int type) {
  switch (type) {
    SQL_CASE(SQL_C_CHAR);
    SQL_CASE(SQL_C_WCHAR);
    SQL_CASE(SQL_C_SHORT);
    SQL_CASE(SQL_C_SSHORT);
    SQL_CASE(SQL_C_USHORT);
    SQL_CASE(SQL_C_LONG);
    SQL_CASE(SQL_C_SLONG);
    SQL_CASE(SQL_C_ULONG);
    SQL_CASE(SQL_C_FLOAT);
    SQL_CASE(SQL_C_DOUBLE);
    SQL_CASE(SQL_C_BIT);
    SQL_CASE(SQL_C_TINYINT);
    SQL_CASE(SQL_C_STINYINT);
    SQL_CASE(SQL_C_UTINYINT);
    SQL_CASE(SQL_C_SBIGINT);
    SQL_CASE(SQL_C_UBIGINT);
    SQL_CASE(SQL_C_BINARY);
    SQL_CASE(SQL_C_DATE);
    SQL_CASE(SQL_C_TIME);
    SQL_CASE(SQL_C_TIMESTAMP);
    SQL_CASE(SQL_C_TYPE_DATE);
    SQL_CASE(SQL_C_TYPE_TIME);
    SQL_CASE(SQL_C_TYPE_TIMESTAMP);
    SQL_CASE(SQL_C_NUMERIC);
    SQL_CASE(SQL_C_GUID);
    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetdiagfield-function?view=sql-server-ver15
const char* sql_diag_identifier(int type) {
  switch (type) {
    // header fields
    SQL_CASE(SQL_DIAG_CURSOR_ROW_COUNT);
    SQL_CASE(SQL_DIAG_DYNAMIC_FUNCTION);
    SQL_CASE(SQL_DIAG_DYNAMIC_FUNCTION_CODE);
    SQL_CASE(SQL_DIAG_NUMBER);
    SQL_CASE(SQL_DIAG_RETURNCODE);
    SQL_CASE(SQL_DIAG_ROW_COUNT);
    // record fields
    SQL_CASE(SQL_DIAG_CLASS_ORIGIN);
    SQL_CASE(SQL_DIAG_COLUMN_NUMBER);
    SQL_CASE(SQL_DIAG_CONNECTION_NAME);
    SQL_CASE(SQL_DIAG_MESSAGE_TEXT);
    SQL_CASE(SQL_DIAG_NATIVE);
    SQL_CASE(SQL_DIAG_ROW_NUMBER);
    SQL_CASE(SQL_DIAG_SERVER_NAME);
    SQL_CASE(SQL_DIAG_SQLSTATE);
    SQL_CASE(SQL_DIAG_SUBCLASS_ORIGIN);
    default: return "UNKNOWN";
  }
}

const char* sql_handle_type(int type) {
  switch(type) {
    SQL_CASE(SQL_HANDLE_ENV);
    SQL_CASE(SQL_HANDLE_DBC);
    SQL_CASE(SQL_HANDLE_STMT);
    SQL_CASE(SQL_HANDLE_DESC);
    // SQL_CASE(SQL_HANDLE_DBC_INFO_TOKEN);
    default: return "UNKNOWN";
  }
}

const char* sql_env_attr_type(int type) {
  switch(type) {
    SQL_CASE(SQL_ATTR_OUTPUT_NTS);
    SQL_CASE(SQL_ATTR_ODBC_VERSION);
    SQL_CASE(SQL_ATTR_CONNECTION_POOLING);
    SQL_CASE(SQL_ATTR_CP_MATCH);
    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function?view=sql-server-ver15
const char* sql_conn_attr_type(int type) {
  switch(type) {
    SQL_CASE(SQL_ATTR_ACCESS_MODE);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_ASYNC_DBC_EVENT);
    SQL_CASE(SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_ASYNC_DBC_PCALLBACK);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_ASYNC_DBC_PCONTEXT);
    SQL_CASE(SQL_ATTR_ASYNC_ENABLE);
    SQL_CASE(SQL_ATTR_AUTO_IPD);
    SQL_CASE(SQL_ATTR_AUTOCOMMIT);
    SQL_CASE(SQL_ATTR_CONNECTION_DEAD);
    SQL_CASE(SQL_ATTR_CONNECTION_TIMEOUT);
    SQL_CASE(SQL_ATTR_CURRENT_CATALOG);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_DBC_INFO_TOKEN);
    SQL_CASE(SQL_ATTR_ENLIST_IN_DTC);
    SQL_CASE(SQL_ATTR_LOGIN_TIMEOUT);
    SQL_CASE(SQL_ATTR_METADATA_ID);
    SQL_CASE(SQL_ATTR_ODBC_CURSORS);
    SQL_CASE(SQL_ATTR_PACKET_SIZE);
    SQL_CASE(SQL_ATTR_QUIET_MODE);
    SQL_CASE(SQL_ATTR_TRACE);
    SQL_CASE(SQL_ATTR_TRACEFILE);
    SQL_CASE(SQL_ATTR_TRANSLATE_LIB);
    SQL_CASE(SQL_ATTR_TRANSLATE_OPTION);
    SQL_CASE(SQL_ATTR_TXN_ISOLATION);
    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15
const char* sql_info_type(int type) {
  switch(type) {
    SQL_CASE(SQL_ACTIVE_ENVIRONMENTS);
    SQL_CASE(SQL_ASYNC_DBC_FUNCTIONS);
    SQL_CASE(SQL_ASYNC_MODE);
    SQL_CASE(SQL_ASYNC_NOTIFICATION);
    SQL_CASE(SQL_BATCH_ROW_COUNT);
    SQL_CASE(SQL_BATCH_SUPPORT);
    SQL_CASE(SQL_DATA_SOURCE_NAME);
    SQL_CASE(SQL_DRIVER_AWARE_POOLING_SUPPORTED);
    SQL_CASE(SQL_DRIVER_HDBC);
    SQL_CASE(SQL_DRIVER_HDESC);
    SQL_CASE(SQL_DRIVER_HENV);
    SQL_CASE(SQL_DRIVER_HLIB);
    SQL_CASE(SQL_DRIVER_HSTMT);
    SQL_CASE(SQL_DRIVER_NAME);
    SQL_CASE(SQL_DRIVER_ODBC_VER);
    SQL_CASE(SQL_DRIVER_VER);
    SQL_CASE(SQL_DYNAMIC_CURSOR_ATTRIBUTES1);
    SQL_CASE(SQL_DYNAMIC_CURSOR_ATTRIBUTES2);
    SQL_CASE(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1);
    SQL_CASE(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2);
    SQL_CASE(SQL_FILE_USAGE);
    SQL_CASE(SQL_GETDATA_EXTENSIONS);
    SQL_CASE(SQL_INFO_SCHEMA_VIEWS);
    SQL_CASE(SQL_KEYSET_CURSOR_ATTRIBUTES1);
    SQL_CASE(SQL_KEYSET_CURSOR_ATTRIBUTES2);
    SQL_CASE(SQL_MAX_ASYNC_CONCURRENT_STATEMENTS);
    SQL_CASE(SQL_MAX_CONCURRENT_ACTIVITIES);
    SQL_CASE(SQL_MAX_DRIVER_CONNECTIONS);
    SQL_CASE(SQL_ODBC_INTERFACE_CONFORMANCE);
    // SQL_CASE(SQL_ODBC_STANDARD_CLI_CONFORMANCE);
    SQL_CASE(SQL_ODBC_VER);
    SQL_CASE(SQL_PARAM_ARRAY_ROW_COUNTS);
    SQL_CASE(SQL_PARAM_ARRAY_SELECTS);
    SQL_CASE(SQL_ROW_UPDATES);
    SQL_CASE(SQL_SEARCH_PATTERN_ESCAPE);
    SQL_CASE(SQL_SERVER_NAME);
    SQL_CASE(SQL_STATIC_CURSOR_ATTRIBUTES1);
    SQL_CASE(SQL_STATIC_CURSOR_ATTRIBUTES2);

    SQL_CASE(SQL_DATABASE_NAME);
    SQL_CASE(SQL_DBMS_NAME);
    SQL_CASE(SQL_DBMS_VER);

    SQL_CASE(SQL_ACCESSIBLE_PROCEDURES);
    SQL_CASE(SQL_ACCESSIBLE_TABLES);
    SQL_CASE(SQL_BOOKMARK_PERSISTENCE);
    SQL_CASE(SQL_CATALOG_TERM);
    SQL_CASE(SQL_COLLATION_SEQ);
    SQL_CASE(SQL_CONCAT_NULL_BEHAVIOR);
    SQL_CASE(SQL_CURSOR_COMMIT_BEHAVIOR);
    SQL_CASE(SQL_CURSOR_ROLLBACK_BEHAVIOR);
    SQL_CASE(SQL_CURSOR_SENSITIVITY);
    SQL_CASE(SQL_DATA_SOURCE_READ_ONLY);
    SQL_CASE(SQL_DEFAULT_TXN_ISOLATION);
    SQL_CASE(SQL_DESCRIBE_PARAMETER);
    SQL_CASE(SQL_MULT_RESULT_SETS);
    SQL_CASE(SQL_MULTIPLE_ACTIVE_TXN);
    SQL_CASE(SQL_NEED_LONG_DATA_LEN);
    SQL_CASE(SQL_NULL_COLLATION);
    SQL_CASE(SQL_PROCEDURE_TERM);
    SQL_CASE(SQL_SCHEMA_TERM);
    SQL_CASE(SQL_SCROLL_OPTIONS);
    SQL_CASE(SQL_TABLE_TERM);
    SQL_CASE(SQL_TXN_CAPABLE);
    SQL_CASE(SQL_TXN_ISOLATION_OPTION);
    SQL_CASE(SQL_USER_NAME);

    SQL_CASE(SQL_AGGREGATE_FUNCTIONS);
    SQL_CASE(SQL_ALTER_DOMAIN);
    // SQL_CASE(SQL_ALTER_SCHEMA);
    SQL_CASE(SQL_ALTER_TABLE);
    // SQL_CASE(SQL_ANSI_SQL_DATETIME_LITERALS);
    SQL_CASE(SQL_CATALOG_LOCATION);
    SQL_CASE(SQL_CATALOG_NAME);
    SQL_CASE(SQL_CATALOG_NAME_SEPARATOR);
    SQL_CASE(SQL_CATALOG_USAGE);
    SQL_CASE(SQL_COLUMN_ALIAS);
    SQL_CASE(SQL_CORRELATION_NAME);
    SQL_CASE(SQL_CREATE_ASSERTION);
    SQL_CASE(SQL_CREATE_CHARACTER_SET);
    SQL_CASE(SQL_CREATE_COLLATION);
    SQL_CASE(SQL_CREATE_DOMAIN);
    SQL_CASE(SQL_CREATE_SCHEMA);
    SQL_CASE(SQL_CREATE_TABLE);
    SQL_CASE(SQL_CREATE_TRANSLATION);
    SQL_CASE(SQL_DDL_INDEX);
    SQL_CASE(SQL_DROP_ASSERTION);
    SQL_CASE(SQL_DROP_CHARACTER_SET);
    SQL_CASE(SQL_DROP_COLLATION);
    SQL_CASE(SQL_DROP_DOMAIN);
    SQL_CASE(SQL_DROP_SCHEMA);

    SQL_CASE(SQL_DROP_TABLE);
    SQL_CASE(SQL_DROP_TRANSLATION);
    SQL_CASE(SQL_DROP_VIEW);
    SQL_CASE(SQL_EXPRESSIONS_IN_ORDERBY);
    SQL_CASE(SQL_GROUP_BY);
    SQL_CASE(SQL_IDENTIFIER_CASE);
    SQL_CASE(SQL_IDENTIFIER_QUOTE_CHAR);
    SQL_CASE(SQL_INDEX_KEYWORDS);
    SQL_CASE(SQL_INSERT_STATEMENT);
    SQL_CASE(SQL_INTEGRITY);
    SQL_CASE(SQL_KEYWORDS);
    SQL_CASE(SQL_LIKE_ESCAPE_CLAUSE);
    SQL_CASE(SQL_NON_NULLABLE_COLUMNS);
    SQL_CASE(SQL_OJ_CAPABILITIES);
    SQL_CASE(SQL_ORDER_BY_COLUMNS_IN_SELECT);
    SQL_CASE(SQL_OUTER_JOINS);
    SQL_CASE(SQL_PROCEDURES);
    SQL_CASE(SQL_QUOTED_IDENTIFIER_CASE);
    SQL_CASE(SQL_SCHEMA_USAGE);
    SQL_CASE(SQL_SPECIAL_CHARACTERS);
    SQL_CASE(SQL_SQL_CONFORMANCE);
    SQL_CASE(SQL_SUBQUERIES);
    SQL_CASE(SQL_UNION);

    SQL_CASE(SQL_MAX_BINARY_LITERAL_LEN);
    SQL_CASE(SQL_MAX_CATALOG_NAME_LEN);
    SQL_CASE(SQL_MAX_CHAR_LITERAL_LEN);
    SQL_CASE(SQL_MAX_COLUMN_NAME_LEN);
    SQL_CASE(SQL_MAX_COLUMNS_IN_GROUP_BY);
    SQL_CASE(SQL_MAX_COLUMNS_IN_INDEX);
    SQL_CASE(SQL_MAX_COLUMNS_IN_ORDER_BY);
    SQL_CASE(SQL_MAX_COLUMNS_IN_SELECT);
    SQL_CASE(SQL_MAX_COLUMNS_IN_TABLE);
    SQL_CASE(SQL_MAX_CURSOR_NAME_LEN);

    SQL_CASE(SQL_MAX_IDENTIFIER_LEN);
    SQL_CASE(SQL_MAX_INDEX_SIZE);
    SQL_CASE(SQL_MAX_PROCEDURE_NAME_LEN);
    SQL_CASE(SQL_MAX_ROW_SIZE);
    SQL_CASE(SQL_MAX_ROW_SIZE_INCLUDES_LONG);
    SQL_CASE(SQL_MAX_SCHEMA_NAME_LEN);
    SQL_CASE(SQL_MAX_STATEMENT_LEN);
    SQL_CASE(SQL_MAX_TABLE_NAME_LEN);
    SQL_CASE(SQL_MAX_TABLES_IN_SELECT);
    SQL_CASE(SQL_MAX_USER_NAME_LEN);

    SQL_CASE(SQL_CONVERT_FUNCTIONS);
    SQL_CASE(SQL_NUMERIC_FUNCTIONS);
    SQL_CASE(SQL_STRING_FUNCTIONS);
    SQL_CASE(SQL_SYSTEM_FUNCTIONS);

    SQL_CASE(SQL_TIMEDATE_ADD_INTERVALS);
    SQL_CASE(SQL_TIMEDATE_DIFF_INTERVALS);
    SQL_CASE(SQL_TIMEDATE_FUNCTIONS);

    SQL_CASE(SQL_CONVERT_BIGINT);
    SQL_CASE(SQL_CONVERT_BINARY);
    SQL_CASE(SQL_CONVERT_BIT);
    SQL_CASE(SQL_CONVERT_CHAR);
    SQL_CASE(SQL_CONVERT_DATE);
    SQL_CASE(SQL_CONVERT_DECIMAL);
    SQL_CASE(SQL_CONVERT_DOUBLE);
    SQL_CASE(SQL_CONVERT_FLOAT);
    SQL_CASE(SQL_CONVERT_INTEGER);
    SQL_CASE(SQL_CONVERT_INTERVAL_DAY_TIME);
    SQL_CASE(SQL_CONVERT_INTERVAL_YEAR_MONTH);

    SQL_CASE(SQL_CONVERT_LONGVARBINARY);
    SQL_CASE(SQL_CONVERT_LONGVARCHAR);
    SQL_CASE(SQL_CONVERT_NUMERIC);
    SQL_CASE(SQL_CONVERT_REAL);
    SQL_CASE(SQL_CONVERT_SMALLINT);
    SQL_CASE(SQL_CONVERT_TIME);
    SQL_CASE(SQL_CONVERT_TIMESTAMP);
    SQL_CASE(SQL_CONVERT_TINYINT);
    SQL_CASE(SQL_CONVERT_VARBINARY);
    SQL_CASE(SQL_CONVERT_VARCHAR);

    SQL_CASE(SQL_DM_VER);

    SQL_CASE(SQL_XOPEN_CLI_YEAR);

    SQL_CASE(SQL_DTC_TRANSITION_COST);

    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolattribute-function?view=sql-server-ver15
const char* sql_field_identifier(int type) {
  switch (type) {
    SQL_CASE(SQL_DESC_AUTO_UNIQUE_VALUE);
    SQL_CASE(SQL_DESC_BASE_COLUMN_NAME);
    SQL_CASE(SQL_DESC_BASE_TABLE_NAME);
    SQL_CASE(SQL_DESC_CASE_SENSITIVE);
    SQL_CASE(SQL_DESC_CATALOG_NAME);
    SQL_CASE(SQL_DESC_CONCISE_TYPE);
    SQL_CASE(SQL_DESC_COUNT);
    SQL_CASE(SQL_DESC_DISPLAY_SIZE);
    SQL_CASE(SQL_DESC_FIXED_PREC_SCALE);
    SQL_CASE(SQL_DESC_LABEL);
    SQL_CASE(SQL_DESC_LENGTH);
    SQL_CASE(SQL_DESC_LITERAL_PREFIX);
    SQL_CASE(SQL_DESC_LITERAL_SUFFIX);
    SQL_CASE(SQL_DESC_LOCAL_TYPE_NAME);
    SQL_CASE(SQL_DESC_NAME);
    SQL_CASE(SQL_DESC_NULLABLE);
    SQL_CASE(SQL_DESC_NUM_PREC_RADIX);
    SQL_CASE(SQL_DESC_OCTET_LENGTH);
    SQL_CASE(SQL_DESC_PRECISION);
    SQL_CASE(SQL_DESC_SCALE);
    SQL_CASE(SQL_DESC_SCHEMA_NAME);
    SQL_CASE(SQL_DESC_SEARCHABLE);
    SQL_CASE(SQL_DESC_TABLE_NAME);
    SQL_CASE(SQL_DESC_TYPE);
    SQL_CASE(SQL_DESC_TYPE_NAME);
    SQL_CASE(SQL_DESC_UNNAMED);
    SQL_CASE(SQL_DESC_UNSIGNED);
    SQL_CASE(SQL_DESC_UPDATABLE);
    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindparameter-function?view=sql-server-ver15
const char* sql_input_output_type(int type) {
  switch (type) {
    SQL_CASE(SQL_PARAM_INPUT);
    SQL_CASE(SQL_PARAM_OUTPUT);
    SQL_CASE(SQL_PARAM_OUTPUT_STREAM);
    SQL_CASE(SQL_PARAM_INPUT_OUTPUT);
    SQL_CASE(SQL_PARAM_INPUT_OUTPUT_STREAM);
    default: return "UNKNOWN";
  }
}

// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function?view=sql-server-ver15
const char* sql_stmt_attr_type(int type) {
  switch (type) {
    SQL_CASE(SQL_ATTR_APP_PARAM_DESC);
    SQL_CASE(SQL_ATTR_APP_ROW_DESC);
    SQL_CASE(SQL_ATTR_ASYNC_ENABLE);
    SQL_CASE(SQL_ATTR_ASYNC_STMT_EVENT);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_ASYNC_STMT_PCALLBACK);
    // ODBC 3.8
    // SQL_CASE(SQL_ATTR_ASYNC_STMT_PCONTEXT);
    SQL_CASE(SQL_ATTR_CONCURRENCY);
    SQL_CASE(SQL_ATTR_CURSOR_SCROLLABLE);
    SQL_CASE(SQL_ATTR_CURSOR_SENSITIVITY);
    SQL_CASE(SQL_ATTR_CURSOR_TYPE);
    SQL_CASE(SQL_ATTR_ENABLE_AUTO_IPD);
    SQL_CASE(SQL_ATTR_FETCH_BOOKMARK_PTR);
    SQL_CASE(SQL_ATTR_IMP_PARAM_DESC);
    SQL_CASE(SQL_ATTR_IMP_ROW_DESC);
    SQL_CASE(SQL_ATTR_KEYSET_SIZE);
    SQL_CASE(SQL_ATTR_MAX_LENGTH);
    SQL_CASE(SQL_ATTR_MAX_ROWS);
    SQL_CASE(SQL_ATTR_METADATA_ID);
    SQL_CASE(SQL_ATTR_NOSCAN);
    SQL_CASE(SQL_ATTR_PARAM_BIND_OFFSET_PTR);
    SQL_CASE(SQL_ATTR_PARAM_BIND_TYPE);
    SQL_CASE(SQL_ATTR_PARAM_OPERATION_PTR);
    SQL_CASE(SQL_ATTR_PARAM_STATUS_PTR);
    SQL_CASE(SQL_ATTR_PARAMS_PROCESSED_PTR);
    SQL_CASE(SQL_ATTR_PARAMSET_SIZE);
    SQL_CASE(SQL_ATTR_QUERY_TIMEOUT);
    SQL_CASE(SQL_ATTR_RETRIEVE_DATA);
    SQL_CASE(SQL_ATTR_ROW_ARRAY_SIZE);
    SQL_CASE(SQL_ATTR_ROW_BIND_OFFSET_PTR);
    SQL_CASE(SQL_ATTR_ROW_BIND_TYPE);
    SQL_CASE(SQL_ATTR_ROW_NUMBER);
    SQL_CASE(SQL_ATTR_ROW_OPERATION_PTR);
    SQL_CASE(SQL_ATTR_ROW_STATUS_PTR);
    SQL_CASE(SQL_ATTR_ROWS_FETCHED_PTR);
    SQL_CASE(SQL_ATTR_SIMULATE_CURSOR);
    SQL_CASE(SQL_ATTR_USE_BOOKMARKS);
    default: return "UNKNOWN";
  }
}

const char* sql_function_type(int type) {
  switch (type) {
    // 
    SQL_CASE(SQL_API_ALL_FUNCTIONS);
    SQL_CASE(SQL_API_ODBC3_ALL_FUNCTIONS);

    // ISO 92 standards-compliance
    SQL_CASE(SQL_API_SQLALLOCHANDLE);
    SQL_CASE(SQL_API_SQLGETDESCFIELD);
    SQL_CASE(SQL_API_SQLBINDCOL);
    SQL_CASE(SQL_API_SQLGETDESCREC);
    SQL_CASE(SQL_API_SQLCANCEL);
    SQL_CASE(SQL_API_SQLGETDIAGFIELD);
    SQL_CASE(SQL_API_SQLCLOSECURSOR);
    SQL_CASE(SQL_API_SQLGETDIAGREC);
    SQL_CASE(SQL_API_SQLCOLATTRIBUTE);
    SQL_CASE(SQL_API_SQLGETENVATTR);
    SQL_CASE(SQL_API_SQLCONNECT);
    SQL_CASE(SQL_API_SQLGETFUNCTIONS);
    SQL_CASE(SQL_API_SQLCOPYDESC);
    SQL_CASE(SQL_API_SQLGETINFO);
    SQL_CASE(SQL_API_SQLDATASOURCES);
    SQL_CASE(SQL_API_SQLGETSTMTATTR);
    SQL_CASE(SQL_API_SQLDESCRIBECOL);
    SQL_CASE(SQL_API_SQLGETTYPEINFO);
    SQL_CASE(SQL_API_SQLDISCONNECT);
    SQL_CASE(SQL_API_SQLNUMRESULTCOLS);
    SQL_CASE(SQL_API_SQLDRIVERS);
    SQL_CASE(SQL_API_SQLPARAMDATA);
    SQL_CASE(SQL_API_SQLENDTRAN);
    SQL_CASE(SQL_API_SQLPREPARE);
    SQL_CASE(SQL_API_SQLEXECDIRECT);
    SQL_CASE(SQL_API_SQLPUTDATA);
    SQL_CASE(SQL_API_SQLEXECUTE);
    SQL_CASE(SQL_API_SQLROWCOUNT);
    SQL_CASE(SQL_API_SQLFETCH);
    SQL_CASE(SQL_API_SQLSETCONNECTATTR);
    SQL_CASE(SQL_API_SQLFETCHSCROLL);
    SQL_CASE(SQL_API_SQLSETCURSORNAME);
    SQL_CASE(SQL_API_SQLFREEHANDLE);
    SQL_CASE(SQL_API_SQLSETDESCFIELD);
    SQL_CASE(SQL_API_SQLFREESTMT);
    SQL_CASE(SQL_API_SQLSETDESCREC);
    SQL_CASE(SQL_API_SQLGETCONNECTATTR);
    SQL_CASE(SQL_API_SQLSETENVATTR);
    SQL_CASE(SQL_API_SQLGETCURSORNAME);
    SQL_CASE(SQL_API_SQLSETSTMTATTR);
    SQL_CASE(SQL_API_SQLGETDATA);

    // Open Group standards-compliance);
    SQL_CASE(SQL_API_SQLCOLUMNS);
    SQL_CASE(SQL_API_SQLSTATISTICS);
    SQL_CASE(SQL_API_SQLSPECIALCOLUMNS);
    SQL_CASE(SQL_API_SQLTABLES);

    // ODBC standards-compliance);
    SQL_CASE(SQL_API_SQLBINDPARAMETER);
    SQL_CASE(SQL_API_SQLNATIVESQL);
    SQL_CASE(SQL_API_SQLBROWSECONNECT);
    SQL_CASE(SQL_API_SQLNUMPARAMS);
    SQL_CASE(SQL_API_SQLBULKOPERATIONS);
    SQL_CASE(SQL_API_SQLPRIMARYKEYS);
    SQL_CASE(SQL_API_SQLCOLUMNPRIVILEGES);
    SQL_CASE(SQL_API_SQLPROCEDURECOLUMNS);
    SQL_CASE(SQL_API_SQLDESCRIBEPARAM);
    SQL_CASE(SQL_API_SQLPROCEDURES);
    SQL_CASE(SQL_API_SQLDRIVERCONNECT);
    SQL_CASE(SQL_API_SQLSETPOS);
    SQL_CASE(SQL_API_SQLFOREIGNKEYS);
    SQL_CASE(SQL_API_SQLTABLEPRIVILEGES);
    SQL_CASE(SQL_API_SQLMORERESULTS);

    SQL_CASE(SQL_API_SQLALLOCCONNECT);
    SQL_CASE(SQL_API_SQLALLOCENV);
    SQL_CASE(SQL_API_SQLALLOCSTMT);
    SQL_CASE(SQL_API_SQLBINDPARAM);
    SQL_CASE(SQL_API_SQLERROR);
    SQL_CASE(SQL_API_SQLFREECONNECT);
    SQL_CASE(SQL_API_SQLFREEENV);
    SQL_CASE(SQL_API_SQLGETCONNECTOPTION);
    SQL_CASE(SQL_API_SQLGETSTMTOPTION);
    SQL_CASE(SQL_API_SQLSETCONNECTOPTION);
    SQL_CASE(SQL_API_SQLSETPARAM);
    SQL_CASE(SQL_API_SQLSETSTMTOPTION);
    SQL_CASE(SQL_API_SQLTRANSACT);
    SQL_CASE(SQL_API_SQLCANCELHANDLE);

    default: return "UNKNOWN";
  }
}

const char* sql_freestmt_option_type(int type) {
  switch (type) {
    SQL_CASE(SQL_CLOSE);
    SQL_CASE(SQL_DROP);
    SQL_CASE(SQL_UNBIND);
    SQL_CASE(SQL_RESET_PARAMS);
    default: return "UNKNOWN";
  }
}

const char* sql_soi_type(int soi) {
  switch (soi) {
    SQL_CASE(SQL_NTS);
    SQL_CASE(SQL_NULL_DATA);
    SQL_CASE(SQL_DEFAULT_PARAM);
    SQL_CASE(SQL_DATA_AT_EXEC);
    default: {
      if (soi >= 0) return "";
      return "SQL_LEN_DATA_AT_EXEC(?)";
    } break;
  }
}

const char* sql_nullable_type(int type) {
  switch (type) {
    SQL_CASE(SQL_NO_NULLS);
    SQL_CASE(SQL_NULLABLE);
    SQL_CASE(SQL_NULLABLE_UNKNOWN);
    default: {
      return "UNKNOWN";
    } break;
  }
}








int is_valid_sql_c_type(int type) {
  const char *ctype = sql_c_type(type);
  if (strcmp(ctype, "UNKNOWN")==0) return 0;
  return 1;
}

int is_valid_sql_sql_type(int type) {
  const char *sqltype = sql_sql_type(type);
  if (strcmp(sqltype, "UNKNOWN")==0) return 0;
  return 1;
}

int utf8_chars(const char *src)
{
  const char *fromcode = "UTF-8";
  const char *tocode = "UCS-2LE";
  iconv_t conv = iconv_open(tocode, fromcode);
  if (!conv) return -1;

  size_t slen = strlen(src);
  char buf[4096];
  size_t dlen = sizeof(buf);
  char *ps = (char*)src;
  char *pd = buf;
  iconv(conv, &ps, &slen, &pd, &dlen);
  DASSERT(slen==0);

  size_t chars = (sizeof(buf) - dlen) / 2;
  iconv_close(conv);
  return (int)chars;
}

static int do_charset_chars(iconv_t cnv, const unsigned char *src)
{
  int chars = 0;
  char *ps = (char*)src;
  char buf[16];
  size_t sn = 1;
  while (1) {
    char  *ds = buf;
    size_t dn = sizeof(buf);
    size_t n = iconv(cnv, &ps, &sn, &ds, &dn);
    if (n==(size_t)-1) {
      int e = errno;
      switch (e) {
        case EILSEQ: return -1;
        case E2BIG:  return -1;
        case EINVAL: sn += 1; continue;
        default:     return -1;
      }
    }
    if (sn)  return -1;
    if (n>0) return -1;
    int i=0;
    for (i=0; i<(sizeof(buf)-dn); ++i) {
      if (buf[i]) break;
    }
    if (i>=(sizeof(buf)-dn)) break;
    chars += (int)1;
    sn = 1;
  }
  return chars;
}

int charset_chars(const char *charset, const unsigned char *src) {
  iconv_t cnv = iconv_open(charset, charset);
  if (cnv==(iconv_t)-1) return -1;

  int chars = do_charset_chars(cnv, src);

  iconv_close(cnv);

  return chars;
}

