text: /*
   * SPDX-License-Identifier: Apache-2.0
   *
   * The OpenSearch Contributors require contributions made to
   * this file be licensed under the Apache-2.0 license or a
   * compatible open source license.
   *
   * Modifications Copyright OpenSearch Contributors. See
   * GitHub history for details.
   */
  
  /*
   * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License").
   * You may not use this file except in compliance with the License.
   * A copy of the License is located at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * or in the "license" file accompanying this file. This file is distributed
   * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
   * express or implied. See the License for the specific language governing
   * permissions and limitations under the License.
   *
   */
  
  #include <stdio.h>
  #include <string.h>
  
  #include "opensearch_odbc.h"
  #include "opensearch_apifunc.h"
  #include "opensearch_connection.h"
  #include "opensearch_driver_connect.h"
  #include "opensearch_info.h"
  #include "statement.h"
  #include "unicode_support.h"
  
  RETCODE SQL_API SQLColumnsW(HSTMT StatementHandle, SQLWCHAR *CatalogName,
                              SQLSMALLINT NameLength1, SQLWCHAR *SchemaName,
                              SQLSMALLINT NameLength2, SQLWCHAR *TableName,
                              SQLSMALLINT NameLength3, SQLWCHAR *ColumnName,
                              SQLSMALLINT NameLength4) {
      CSTR func = "SQLColumnsW";
      RETCODE ret;
      char *ctName, *scName, *tbName, *clName;
      SQLLEN nmlen1, nmlen2, nmlen3, nmlen4;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = PODBC_SEARCH_PUBLIC_SCHEMA;
      ConnInfo *ci;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      ci = &(conn->connInfo);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(CatalogName, NameLength1, &nmlen1, lower_id);
      scName = ucs2_to_utf8(SchemaName, NameLength2, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(TableName, NameLength3, &nmlen3, lower_id);
      clName = ucs2_to_utf8(ColumnName, NameLength4, &nmlen4, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_Columns(StatementHandle, (SQLCHAR *)ctName,
                              (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
                              (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName,
                              (SQLSMALLINT)nmlen3, (SQLCHAR *)clName,
                              (SQLSMALLINT)nmlen4, flag, 0, 0);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      if (clName)
          free(clName);
      return ret;
  }
  
  RETCODE SQL_API SQLConnectW(HDBC ConnectionHandle, SQLWCHAR *ServerName,
                              SQLSMALLINT NameLength1, SQLWCHAR *UserName,
                              SQLSMALLINT NameLength2, SQLWCHAR *Authentication,
                              SQLSMALLINT NameLength3) {
      char *svName, *usName, *auth;
      SQLLEN nmlen1, nmlen2, nmlen3;
      RETCODE ret;
      ConnectionClass *conn = (ConnectionClass *)ConnectionHandle;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      CC_set_in_unicode_driver(conn);
      svName = ucs2_to_utf8(ServerName, NameLength1, &nmlen1, FALSE);
      usName = ucs2_to_utf8(UserName, NameLength2, &nmlen2, FALSE);
      auth = ucs2_to_utf8(Authentication, NameLength3, &nmlen3, FALSE);
      ret =
          OPENSEARCHAPI_Connect(ConnectionHandle, (SQLCHAR *)svName, (SQLSMALLINT)nmlen1,
                        (SQLCHAR *)usName, (SQLSMALLINT)nmlen2, (SQLCHAR *)auth,
                        (SQLSMALLINT)nmlen3);
      LEAVE_CONN_CS(conn);
      if (svName)
          free(svName);
      if (usName)
          free(usName);
      if (auth)
          free(auth);
      return ret;
  }
  
  RETCODE SQL_API SQLDriverConnectW(HDBC hdbc, HWND hwnd, SQLWCHAR *szConnStrIn,
                                    SQLSMALLINT cbConnStrIn,
                                    SQLWCHAR *szConnStrOut,
                                    SQLSMALLINT cbConnStrOutMax,
                                    SQLSMALLINT *pcbConnStrOut,
                                    SQLUSMALLINT fDriverCompletion) {
      CSTR func = "SQLDriverConnectW";
      char *szIn, *szOut = NULL;
      SQLSMALLINT maxlen, obuflen = 0;
      SQLLEN inlen;
      SQLSMALLINT olen, *pCSO;
      RETCODE ret;
      ConnectionClass *conn = (ConnectionClass *)hdbc;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      CC_set_in_unicode_driver(conn);
      szIn = ucs2_to_utf8(szConnStrIn, cbConnStrIn, &inlen, FALSE);
      maxlen = cbConnStrOutMax;
      pCSO = NULL;
      olen = 0;
      if (maxlen > 0) {
          obuflen = maxlen + 1;
          szOut = malloc(obuflen);
          if (!szOut) {
              CC_set_error(conn, CONN_NO_MEMORY_ERROR,
                           "Could not allocate memory for output buffer", func);
              ret = SQL_ERROR;
              goto cleanup;
          }
          pCSO = &olen;
      } else if (pcbConnStrOut)
          pCSO = &olen;
      ret =
          OPENSEARCHAPI_DriverConnect(hdbc, hwnd, (SQLCHAR *)szIn, (SQLSMALLINT)inlen,
                              (SQLCHAR *)szOut, maxlen, pCSO, fDriverCompletion);
      if (ret != SQL_ERROR && NULL != pCSO) {
          SQLLEN outlen = olen;
  
          if (olen < obuflen)
              outlen = utf8_to_ucs2(szOut, olen, szConnStrOut, cbConnStrOutMax);
          else
              utf8_to_ucs2(szOut, maxlen, szConnStrOut, cbConnStrOutMax);
          if (outlen >= cbConnStrOutMax && NULL != szConnStrOut
              && NULL != pcbConnStrOut) {
              MYLOG(OPENSEARCH_ALL, "cbConnstrOutMax=%d pcb=%p\n", cbConnStrOutMax,
                    pcbConnStrOut);
              if (SQL_SUCCESS == ret) {
                  CC_set_error(conn, CONN_TRUNCATED,
                               "the ConnStrOut is too small", func);
                  ret = SQL_SUCCESS_WITH_INFO;
              }
          }
          if (pcbConnStrOut)
              *pcbConnStrOut = (SQLSMALLINT)outlen;
      }
  cleanup:
      LEAVE_CONN_CS(conn);
      if (szOut)
          free(szOut);
      if (szIn)
          free(szIn);
      return ret;
  }
  RETCODE SQL_API SQLBrowseConnectW(HDBC hdbc, SQLWCHAR *szConnStrIn,
                                    SQLSMALLINT cbConnStrIn,
                                    SQLWCHAR *szConnStrOut,
                                    SQLSMALLINT cbConnStrOutMax,
                                    SQLSMALLINT *pcbConnStrOut) {
      CSTR func = "SQLBrowseConnectW";
      char *szIn, *szOut;
      SQLLEN inlen;
      SQLUSMALLINT obuflen;
      SQLSMALLINT olen = 0;
      RETCODE ret;
      ConnectionClass *conn = (ConnectionClass *)hdbc;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      CC_set_in_unicode_driver(conn);
      szIn = ucs2_to_utf8(szConnStrIn, cbConnStrIn, &inlen, FALSE);
      obuflen = cbConnStrOutMax + 1;
      szOut = malloc(obuflen);
      if (szOut)
          ret = OPENSEARCHAPI_BrowseConnect(hdbc, (SQLCHAR *)szIn, (SQLSMALLINT)inlen,
                                    (SQLCHAR *)szOut, cbConnStrOutMax, &olen);
      else {
          CC_set_error(conn, CONN_NO_MEMORY_ERROR,
                       "Could not allocate memory for output buffer", func);
          ret = SQL_ERROR;
      }
      LEAVE_CONN_CS(conn);
      if (ret != SQL_ERROR) {
          SQLLEN outlen =
              utf8_to_ucs2(szOut, olen, szConnStrOut, cbConnStrOutMax);
          if (pcbConnStrOut)
              *pcbConnStrOut = (SQLSMALLINT)outlen;
      }
      free(szOut);
      if (szIn)
          free(szIn);
      return ret;
  }
  
  RETCODE SQL_API SQLDataSourcesW(HENV EnvironmentHandle, SQLUSMALLINT Direction,
                                  SQLWCHAR *ServerName, SQLSMALLINT BufferLength1,
                                  SQLSMALLINT *NameLength1, SQLWCHAR *Description,
                                  SQLSMALLINT BufferLength2,
                                  SQLSMALLINT *NameLength2) {
      UNUSED(EnvironmentHandle, Direction, ServerName, BufferLength1, NameLength1,
             Description, BufferLength2, NameLength2);
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      return SQL_ERROR;
  }
  
  RETCODE SQL_API SQLDescribeColW(HSTMT StatementHandle,
                                  SQLUSMALLINT ColumnNumber, SQLWCHAR *ColumnName,
                                  SQLSMALLINT BufferLength,
                                  SQLSMALLINT *NameLength, SQLSMALLINT *DataType,
                                  SQLULEN *ColumnSize, SQLSMALLINT *DecimalDigits,
                                  SQLSMALLINT *Nullable) {
      CSTR func = "SQLDescribeColW";
      RETCODE ret;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      SQLSMALLINT buflen, nmlen = 0;
      char *clName = NULL, *clNamet = NULL;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      buflen = 0;
      if (BufferLength > 0)
          buflen = BufferLength * 3;
      else if (NameLength)
          buflen = 32;
      if (buflen > 0)
          clNamet = malloc(buflen);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      for (;; buflen = nmlen + 1, clNamet = realloc(clName, buflen)) {
          if (!clNamet) {
              SC_set_error(stmt, STMT_NO_MEMORY_ERROR,
                           "Could not allocate memory for column name", func);
              ret = SQL_ERROR;
              break;
          }
          clName = clNamet;
          ret = OPENSEARCHAPI_DescribeCol(StatementHandle, ColumnNumber,
                                  (SQLCHAR *)clName, buflen, &nmlen, DataType,
                                  ColumnSize, DecimalDigits, Nullable);
          if (SQL_SUCCESS_WITH_INFO != ret || nmlen < buflen)
              break;
      }
      if (SQL_SUCCEEDED(ret)) {
          SQLLEN nmcount = nmlen;
  
          if (nmlen < buflen)
              nmcount = utf8_to_ucs2(clName, nmlen, ColumnName, BufferLength);
          if (SQL_SUCCESS == ret && BufferLength > 0 && nmcount > BufferLength) {
              ret = SQL_SUCCESS_WITH_INFO;
              SC_set_error(stmt, STMT_TRUNCATED, "Column name too large", func);
          }
          if (NameLength)
              *NameLength = (SQLSMALLINT)nmcount;
      }
      LEAVE_STMT_CS(stmt);
      if (clName)
          free(clName);
      return ret;
  }
  
  RETCODE SQL_API SQLExecDirectW(HSTMT StatementHandle, SQLWCHAR *StatementText,
                                 SQLINTEGER TextLength) {
      if (StatementHandle == NULL)
          return SQL_ERROR;
  
      StatementClass *stmt = (StatementClass *)StatementHandle;
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      // Get query string
      SQLLEN slen = 0;
      char *stxt = ucs2_to_utf8(StatementText, TextLength, &slen, FALSE);
  
      // Enter critical
      ENTER_STMT_CS(stmt);
  
      // Clear error and rollback
      SC_clear_error(stmt);
  
      // Execute statement if statement is ready
      RETCODE ret = SQL_ERROR;
      if (!SC_opencheck(stmt, "SQLExecDirectW"))
          ret = OPENSEARCHAPI_ExecDirect(StatementHandle, (const SQLCHAR *)stxt,
                                 (SQLINTEGER)slen, 1);
  
      // Exit critical
      LEAVE_STMT_CS(stmt);
  
      if (stxt)
          free(stxt);
      return ret;
  }
  
  RETCODE SQL_API SQLGetCursorNameW(HSTMT StatementHandle, SQLWCHAR *CursorName,
                                    SQLSMALLINT BufferLength,
                                    SQLSMALLINT *NameLength) {
      CSTR func = "SQLGetCursorNameW";
      RETCODE ret;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      char *crName = NULL, *crNamet;
      SQLSMALLINT clen = 0, buflen;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (BufferLength > 0)
          buflen = BufferLength * 3;
      else
          buflen = 32;
      crNamet = malloc(buflen);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      for (;; buflen = clen + 1, crNamet = realloc(crName, buflen)) {
          if (!crNamet) {
              SC_set_error(stmt, STMT_NO_MEMORY_ERROR,
                           "Could not allocate memory for cursor name", func);
              ret = SQL_ERROR;
              break;
          }
          crName = crNamet;
          ret = OPENSEARCHAPI_GetCursorName(StatementHandle, (SQLCHAR *)crName, buflen,
                                    &clen);
          if (SQL_SUCCESS_WITH_INFO != ret || clen < buflen)
              break;
      }
      if (SQL_SUCCEEDED(ret)) {
          SQLLEN nmcount = clen;
  
          if (clen < buflen)
              nmcount = utf8_to_ucs2(crName, clen, CursorName, BufferLength);
          if (SQL_SUCCESS == ret && nmcount > BufferLength) {
              ret = SQL_SUCCESS_WITH_INFO;
              SC_set_error(stmt, STMT_TRUNCATED, "Cursor name too large", func);
          }
          if (NameLength)
              *NameLength = (SQLSMALLINT)nmcount;
      }
      LEAVE_STMT_CS(stmt);
      free(crName);
      return ret;
  }
  
  RETCODE SQL_API SQLGetInfoW(HDBC ConnectionHandle, SQLUSMALLINT InfoType,
                              PTR InfoValue, SQLSMALLINT BufferLength,
                              SQLSMALLINT *StringLength) {
      ConnectionClass *conn = (ConnectionClass *)ConnectionHandle;
      RETCODE ret;
  
      ENTER_CONN_CS(conn);
      CC_set_in_unicode_driver(conn);
      CC_clear_error(conn);
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if ((ret = OPENSEARCHAPI_GetInfo(ConnectionHandle, InfoType, InfoValue,
                               BufferLength, StringLength))
          == SQL_ERROR)
          CC_log_error("SQLGetInfoW", "", conn);
      LEAVE_CONN_CS(conn);
      return ret;
  }
  
  RETCODE SQL_API SQLPrepareW(HSTMT StatementHandle, SQLWCHAR *StatementText,
                              SQLINTEGER TextLength) {
      if (StatementHandle == NULL)
          return SQL_ERROR;
  
      CSTR func = "SQLPrepareW";
      StatementClass *stmt = (StatementClass *)StatementHandle;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      SQLLEN slen;
      char *stxt = ucs2_to_utf8(StatementText, TextLength, &slen, FALSE);
  
      // Enter critical
      ENTER_STMT_CS(stmt);
  
      // Clear error and rollback
      SC_clear_error(stmt);
  
      // Prepare statement if statement is ready
      RETCODE ret = SQL_ERROR;
      if (!SC_opencheck(stmt, func))
          ret = OPENSEARCHAPI_Prepare(StatementHandle, (const SQLCHAR *)stxt,
                              (SQLINTEGER)slen);
  
      // Exit critical
      LEAVE_STMT_CS(stmt);
  
      // Release memory
      if (stxt)
          free(stxt);
      return ret;
  }
  
  RETCODE SQL_API SQLSetCursorNameW(HSTMT StatementHandle, SQLWCHAR *CursorName,
                                    SQLSMALLINT NameLength) {
      RETCODE ret;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      char *crName;
      SQLLEN nlen;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      crName = ucs2_to_utf8(CursorName, NameLength, &nlen, FALSE);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      ret = OPENSEARCHAPI_SetCursorName(StatementHandle, (SQLCHAR *)crName,
                                (SQLSMALLINT)nlen);
      LEAVE_STMT_CS(stmt);
      if (crName)
          free(crName);
      return ret;
  }
  
  RETCODE SQL_API SQLSpecialColumnsW(
      HSTMT StatementHandle, SQLUSMALLINT IdentifierType, SQLWCHAR *CatalogName,
      SQLSMALLINT NameLength1, SQLWCHAR *SchemaName, SQLSMALLINT NameLength2,
      SQLWCHAR *TableName, SQLSMALLINT NameLength3, SQLUSMALLINT Scope,
      SQLUSMALLINT Nullable) {
      CSTR func = "SQLSpecialColumnsW";
      RETCODE ret;
      char *ctName, *scName, *tbName;
      SQLLEN nmlen1, nmlen2, nmlen3;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      ConnectionClass *conn;
      BOOL lower_id;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(CatalogName, NameLength1, &nmlen1, lower_id);
      scName = ucs2_to_utf8(SchemaName, NameLength2, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(TableName, NameLength3, &nmlen3, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_SpecialColumns(
              StatementHandle, IdentifierType, (SQLCHAR *)ctName,
              (SQLSMALLINT)nmlen1, (SQLCHAR *)scName, (SQLSMALLINT)nmlen2,
              (SQLCHAR *)tbName, (SQLSMALLINT)nmlen3, Scope, Nullable);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      return ret;
  }
  
  RETCODE SQL_API SQLStatisticsW(HSTMT StatementHandle, SQLWCHAR *CatalogName,
                                 SQLSMALLINT NameLength1, SQLWCHAR *SchemaName,
                                 SQLSMALLINT NameLength2, SQLWCHAR *TableName,
                                 SQLSMALLINT NameLength3, SQLUSMALLINT Unique,
                                 SQLUSMALLINT Reserved) {
      CSTR func = "SQLStatisticsW";
      RETCODE ret;
      char *ctName, *scName, *tbName;
      SQLLEN nmlen1, nmlen2, nmlen3;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      ConnectionClass *conn;
      BOOL lower_id;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(CatalogName, NameLength1, &nmlen1, lower_id);
      scName = ucs2_to_utf8(SchemaName, NameLength2, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(TableName, NameLength3, &nmlen3, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_Statistics(StatementHandle, (SQLCHAR *)ctName,
                                 (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
                                 (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName,
                                 (SQLSMALLINT)nmlen3, Unique, Reserved);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      return ret;
  }
  
  RETCODE SQL_API SQLTablesW(HSTMT StatementHandle, SQLWCHAR *CatalogName,
                             SQLSMALLINT NameLength1, SQLWCHAR *SchemaName,
                             SQLSMALLINT NameLength2, SQLWCHAR *TableName,
                             SQLSMALLINT NameLength3, SQLWCHAR *TableType,
                             SQLSMALLINT NameLength4) {
      CSTR func = "SQLTablesW";
      RETCODE ret;
      char *ctName, *scName, *tbName, *tbType;
      SQLLEN nmlen1, nmlen2, nmlen3, nmlen4;
      StatementClass *stmt = (StatementClass *)StatementHandle;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = 0;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(CatalogName, NameLength1, &nmlen1, lower_id);
      scName = ucs2_to_utf8(SchemaName, NameLength2, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(TableName, NameLength3, &nmlen3, lower_id);
      tbType = ucs2_to_utf8(TableType, NameLength4, &nmlen4, FALSE);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_Tables(
              StatementHandle, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1,
              (SQLCHAR *)scName, (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName,
              (SQLSMALLINT)nmlen3, (SQLCHAR *)tbType, (SQLSMALLINT)nmlen4, flag);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      if (tbType)
          free(tbType);
      return ret;
  }
  
  RETCODE SQL_API SQLColumnPrivilegesW(
      HSTMT hstmt, SQLWCHAR *szCatalogName, SQLSMALLINT cbCatalogName,
      SQLWCHAR *szSchemaName, SQLSMALLINT cbSchemaName, SQLWCHAR *szTableName,
      SQLSMALLINT cbTableName, SQLWCHAR *szColumnName, SQLSMALLINT cbColumnName) {
      CSTR func = "SQLColumnPrivilegesW";
      RETCODE ret;
      char *ctName, *scName, *tbName, *clName;
      SQLLEN nmlen1, nmlen2, nmlen3, nmlen4;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = 0;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szCatalogName, cbCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szSchemaName, cbSchemaName, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(szTableName, cbTableName, &nmlen3, lower_id);
      clName = ucs2_to_utf8(szColumnName, cbColumnName, &nmlen4, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_ColumnPrivileges(
              hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
              (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName, (SQLSMALLINT)nmlen3,
              (SQLCHAR *)clName, (SQLSMALLINT)nmlen4, flag);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      if (clName)
          free(clName);
      return ret;
  }
  
  RETCODE SQL_API SQLForeignKeysW(
      HSTMT hstmt, SQLWCHAR *szPkCatalogName, SQLSMALLINT cbPkCatalogName,
      SQLWCHAR *szPkSchemaName, SQLSMALLINT cbPkSchemaName,
      SQLWCHAR *szPkTableName, SQLSMALLINT cbPkTableName,
      SQLWCHAR *szFkCatalogName, SQLSMALLINT cbFkCatalogName,
      SQLWCHAR *szFkSchemaName, SQLSMALLINT cbFkSchemaName,
      SQLWCHAR *szFkTableName, SQLSMALLINT cbFkTableName) {
      CSTR func = "SQLForeignKeysW";
      RETCODE ret;
      char *ctName, *scName, *tbName, *fkctName, *fkscName, *fktbName;
      SQLLEN nmlen1, nmlen2, nmlen3, nmlen4, nmlen5, nmlen6;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szPkCatalogName, cbPkCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szPkSchemaName, cbPkSchemaName, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(szPkTableName, cbPkTableName, &nmlen3, lower_id);
      fkctName =
          ucs2_to_utf8(szFkCatalogName, cbFkCatalogName, &nmlen4, lower_id);
      fkscName = ucs2_to_utf8(szFkSchemaName, cbFkSchemaName, &nmlen5, lower_id);
      fktbName = ucs2_to_utf8(szFkTableName, cbFkTableName, &nmlen6, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_ForeignKeys(
              hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
              (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName, (SQLSMALLINT)nmlen3,
              (SQLCHAR *)fkctName, (SQLSMALLINT)nmlen4, (SQLCHAR *)fkscName,
              (SQLSMALLINT)nmlen5, (SQLCHAR *)fktbName, (SQLSMALLINT)nmlen6);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      if (fkctName)
          free(fkctName);
      if (fkscName)
          free(fkscName);
      if (fktbName)
          free(fktbName);
      return ret;
  }
  
  RETCODE SQL_API SQLNativeSqlW(HDBC hdbc, SQLWCHAR *szSqlStrIn,
                                SQLINTEGER cbSqlStrIn, SQLWCHAR *szSqlStr,
                                SQLINTEGER cbSqlStrMax, SQLINTEGER *pcbSqlStr) {
      CSTR func = "SQLNativeSqlW";
      RETCODE ret;
      char *szIn, *szOut = NULL, *szOutt = NULL;
      SQLLEN slen;
      SQLINTEGER buflen, olen = 0;
      ConnectionClass *conn = (ConnectionClass *)hdbc;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      CC_set_in_unicode_driver(conn);
      szIn = ucs2_to_utf8(szSqlStrIn, cbSqlStrIn, &slen, FALSE);
      buflen = 3 * cbSqlStrMax;
      if (buflen > 0)
          szOutt = malloc(buflen);
      for (;; buflen = olen + 1, szOutt = realloc(szOut, buflen)) {
          if (!szOutt) {
              CC_set_error(conn, CONN_NO_MEMORY_ERROR,
                           "Could not allocate memory for output buffer", func);
              ret = SQL_ERROR;
              break;
          }
          szOut = szOutt;
          ret = OPENSEARCHAPI_NativeSql(hdbc, (SQLCHAR *)szIn, (SQLINTEGER)slen,
                                (SQLCHAR *)szOut, buflen, &olen);
          if (SQL_SUCCESS_WITH_INFO != ret || olen < buflen)
              break;
      }
      if (szIn)
          free(szIn);
      if (SQL_SUCCEEDED(ret)) {
          SQLLEN szcount = olen;
  
          if (olen < buflen)
              szcount = utf8_to_ucs2(szOut, olen, szSqlStr, cbSqlStrMax);
          if (SQL_SUCCESS == ret && szcount > cbSqlStrMax) {
              ret = SQL_SUCCESS_WITH_INFO;
              CC_set_error(conn, CONN_TRUNCATED, "Sql string too large", func);
          }
          if (pcbSqlStr)
              *pcbSqlStr = (SQLINTEGER)szcount;
      }
      LEAVE_CONN_CS(conn);
      free(szOut);
      return ret;
  }
  
  RETCODE SQL_API SQLPrimaryKeysW(HSTMT hstmt, SQLWCHAR *szCatalogName,
                                  SQLSMALLINT cbCatalogName,
                                  SQLWCHAR *szSchemaName,
                                  SQLSMALLINT cbSchemaName, SQLWCHAR *szTableName,
                                  SQLSMALLINT cbTableName) {
      CSTR func = "SQLPrimaryKeysW";
      RETCODE ret;
      char *ctName, *scName, *tbName;
      SQLLEN nmlen1, nmlen2, nmlen3;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szCatalogName, cbCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szSchemaName, cbSchemaName, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(szTableName, cbTableName, &nmlen3, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_PrimaryKeys(hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1,
                                  (SQLCHAR *)scName, (SQLSMALLINT)nmlen2,
                                  (SQLCHAR *)tbName, (SQLSMALLINT)nmlen3, 0);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      return ret;
  }
  
  RETCODE SQL_API SQLProcedureColumnsW(
      HSTMT hstmt, SQLWCHAR *szCatalogName, SQLSMALLINT cbCatalogName,
      SQLWCHAR *szSchemaName, SQLSMALLINT cbSchemaName, SQLWCHAR *szProcName,
      SQLSMALLINT cbProcName, SQLWCHAR *szColumnName, SQLSMALLINT cbColumnName) {
      CSTR func = "SQLProcedureColumnsW";
      RETCODE ret;
      char *ctName, *scName, *prName, *clName;
      SQLLEN nmlen1, nmlen2, nmlen3, nmlen4;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = 0;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szCatalogName, cbCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szSchemaName, cbSchemaName, &nmlen2, lower_id);
      prName = ucs2_to_utf8(szProcName, cbProcName, &nmlen3, lower_id);
      clName = ucs2_to_utf8(szColumnName, cbColumnName, &nmlen4, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_ProcedureColumns(
              hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
              (SQLSMALLINT)nmlen2, (SQLCHAR *)prName, (SQLSMALLINT)nmlen3,
              (SQLCHAR *)clName, (SQLSMALLINT)nmlen4, flag);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (prName)
          free(prName);
      if (clName)
          free(clName);
      return ret;
  }
  
  RETCODE SQL_API SQLProceduresW(HSTMT hstmt, SQLWCHAR *szCatalogName,
                                 SQLSMALLINT cbCatalogName,
                                 SQLWCHAR *szSchemaName, SQLSMALLINT cbSchemaName,
                                 SQLWCHAR *szProcName, SQLSMALLINT cbProcName) {
      CSTR func = "SQLProceduresW";
      RETCODE ret;
      char *ctName, *scName, *prName;
      SQLLEN nmlen1, nmlen2, nmlen3;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = 0;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szCatalogName, cbCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szSchemaName, cbSchemaName, &nmlen2, lower_id);
      prName = ucs2_to_utf8(szProcName, cbProcName, &nmlen3, lower_id);
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_Procedures(hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1,
                                 (SQLCHAR *)scName, (SQLSMALLINT)nmlen2,
                                 (SQLCHAR *)prName, (SQLSMALLINT)nmlen3, flag);
      LEAVE_STMT_CS(stmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (prName)
          free(prName);
      return ret;
  }
  
  RETCODE SQL_API SQLTablePrivilegesW(HSTMT hstmt, SQLWCHAR *szCatalogName,
                                      SQLSMALLINT cbCatalogName,
                                      SQLWCHAR *szSchemaName,
                                      SQLSMALLINT cbSchemaName,
                                      SQLWCHAR *szTableName,
                                      SQLSMALLINT cbTableName) {
      CSTR func = "SQLTablePrivilegesW";
      RETCODE ret;
      char *ctName, *scName, *tbName;
      SQLLEN nmlen1, nmlen2, nmlen3;
      StatementClass *stmt = (StatementClass *)hstmt;
      ConnectionClass *conn;
      BOOL lower_id;
      UWORD flag = 0;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      conn = SC_get_conn(stmt);
      lower_id = DEFAULT_LOWERCASEIDENTIFIER;
      ctName = ucs2_to_utf8(szCatalogName, cbCatalogName, &nmlen1, lower_id);
      scName = ucs2_to_utf8(szSchemaName, cbSchemaName, &nmlen2, lower_id);
      tbName = ucs2_to_utf8(szTableName, cbTableName, &nmlen3, lower_id);
      ENTER_STMT_CS((StatementClass *)hstmt);
      SC_clear_error(stmt);
      if (stmt->options.metadata_id)
          flag |= PODBC_NOT_SEARCH_PATTERN;
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_TablePrivileges(
              hstmt, (SQLCHAR *)ctName, (SQLSMALLINT)nmlen1, (SQLCHAR *)scName,
              (SQLSMALLINT)nmlen2, (SQLCHAR *)tbName, (SQLSMALLINT)nmlen3, flag);
      LEAVE_STMT_CS((StatementClass *)hstmt);
      if (ctName)
          free(ctName);
      if (scName)
          free(scName);
      if (tbName)
          free(tbName);
      return ret;
  }
  
  RETCODE SQL_API SQLGetTypeInfoW(SQLHSTMT StatementHandle,
                                  SQLSMALLINT DataType) {
      CSTR func = "SQLGetTypeInfoW";
      RETCODE ret;
      StatementClass *stmt = (StatementClass *)StatementHandle;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      if (SC_opencheck(stmt, func))
          ret = SQL_ERROR;
      else
          ret = OPENSEARCHAPI_GetTypeInfo(StatementHandle, DataType);
      LEAVE_STMT_CS(stmt);
      return ret;
  }
  
  /* ODBC 2.x-specific functions */
  // TODO (#590): Add implementations for remaining ODBC 2.x function
  
  SQLRETURN SQL_API SQLColAttributesW(SQLHSTMT hstmt, SQLUSMALLINT iCol,
                                      SQLUSMALLINT iField, SQLPOINTER pCharAttr,
                                      SQLSMALLINT cbCharAttrMax,
                                      SQLSMALLINT *pcbCharAttr,
  #if defined(_WIN64) || defined(_WIN32) || defined(SQLCOLATTRIBUTE_SQLLEN)
                                      SQLLEN *pNumAttr
  #else
                                      SQLPOINTER pNumAttr
  #endif
  ) {
      CSTR func = "SQLColAttributeW";
      RETCODE ret;
      StatementClass *stmt = (StatementClass *)hstmt;
      SQLSMALLINT *rgbL, blen = 0, bMax;
      char *rgbD = NULL, *rgbDt;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      if (SC_connection_lost_check(stmt, __FUNCTION__))
          return SQL_ERROR;
  
      ENTER_STMT_CS(stmt);
      SC_clear_error(stmt);
      switch (iField) {
          case SQL_DESC_BASE_COLUMN_NAME:
          case SQL_DESC_BASE_TABLE_NAME:
          case SQL_DESC_CATALOG_NAME:
          case SQL_DESC_LABEL:
          case SQL_DESC_LITERAL_PREFIX:
          case SQL_DESC_LITERAL_SUFFIX:
          case SQL_DESC_LOCAL_TYPE_NAME:
          case SQL_DESC_NAME:
          case SQL_DESC_SCHEMA_NAME:
          case SQL_DESC_TABLE_NAME:
          case SQL_DESC_TYPE_NAME:
          case SQL_COLUMN_NAME:
              bMax = cbCharAttrMax * 3 / WCLEN;
              rgbD = malloc(bMax);
              rgbL = &blen;
              for (rgbDt = rgbD;; bMax = blen + 1, rgbDt = realloc(rgbD, bMax)) {
                  if (!rgbDt) {
                      ret = SQL_ERROR;
                      break;
                  }
                  rgbD = rgbDt;
                  ret = OPENSEARCHAPI_ColAttributes(hstmt, iCol, iField, rgbD, bMax, rgbL,
                                            pNumAttr);
                  if (SQL_SUCCESS_WITH_INFO != ret || blen < bMax)
                      break;
              }
              if (SQL_SUCCEEDED(ret)) {
                  blen = (SQLSMALLINT)utf8_to_ucs2(
                      rgbD, blen, (SQLWCHAR *)pCharAttr, cbCharAttrMax / WCLEN);
                  if (SQL_SUCCESS == ret
                      && blen * WCLEN >= (unsigned long)cbCharAttrMax) {
                      ret = SQL_SUCCESS_WITH_INFO;
                      SC_set_error(stmt, STMT_TRUNCATED,
                                   "The buffer was too small for the pCharAttr.",
                                   func);
                  }
                  if (pcbCharAttr)
                      *pcbCharAttr = blen * WCLEN;
              }
              if (rgbD)
                  free(rgbD);
              break;
          default:
              rgbD = pCharAttr;
              bMax = cbCharAttrMax;
              rgbL = pcbCharAttr;
              ret = OPENSEARCHAPI_ColAttributes(hstmt, iCol, iField, rgbD, bMax, rgbL,
                                        pNumAttr);
              break;
      }
      LEAVE_STMT_CS(stmt);
  
      return ret;
  }
  
  RETCODE SQL_API SQLGetConnectOptionW(HDBC ConnectionHandle, SQLUSMALLINT Option,
                                       PTR Value) {
      ConnectionClass *conn = (ConnectionClass *)ConnectionHandle;
      RETCODE ret;
  
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      MYLOG(OPENSEARCH_TRACE, "entering " FORMAT_UINTEGER "\n", Option);
      ret = OPENSEARCHAPI_GetConnectOption(ConnectionHandle, Option, Value, NULL, 0);
      LEAVE_CONN_CS(conn);
      return ret;
  }
  
  RETCODE SQL_API SQLSetConnectOptionW(HDBC ConnectionHandle, SQLUSMALLINT Option,
                                       SQLULEN Value) {
      ConnectionClass *conn = (ConnectionClass *)ConnectionHandle;
      RETCODE ret;
  
      MYLOG(OPENSEARCH_TRACE, "entering " FORMAT_INTEGER "\n", Option);
      ENTER_CONN_CS(conn);
      CC_clear_error(conn);
      ret = OPENSEARCHAPI_SetConnectOption(ConnectionHandle, Option, Value);
      LEAVE_CONN_CS(conn);
      return ret;
  }
  
  RETCODE SQL_API SQLErrorW(SQLHENV EnvironmentHandle, SQLHDBC ConnectionHandle,
                            SQLHSTMT StatementHandle, SQLWCHAR *Sqlstate,
                            SQLINTEGER *NativeError, SQLWCHAR *MessageText,
                            SQLSMALLINT BufferLength, SQLSMALLINT *TextLength) {
      RETCODE ret;
      SQLSMALLINT buflen;
      SQLSMALLINT tlen = 0;
      SQLSMALLINT RecNumber = 1;
      char qstr_ansi[8], *mtxt = NULL;
  
      MYLOG(OPENSEARCH_TRACE, "entering\n");
      buflen = 0;
      if (MessageText && BufferLength > 0) {
          buflen = BufferLength;
          mtxt = malloc(buflen);
      }
  
      if (StatementHandle) {
          ret = OPENSEARCHAPI_StmtError(StatementHandle, RecNumber, (SQLCHAR *)qstr_ansi,
                                NativeError, (SQLCHAR *)mtxt, buflen, &tlen, 0);
      } else if (ConnectionHandle) {
          ret = OPENSEARCHAPI_ConnectError(ConnectionHandle, RecNumber,
                                   (SQLCHAR *)qstr_ansi, NativeError,
                                   (SQLCHAR *)mtxt, buflen, &tlen, 0);
      } else if (EnvironmentHandle) {
          ret = OPENSEARCHAPI_EnvError(EnvironmentHandle, RecNumber, (SQLCHAR *)qstr_ansi,
                               NativeError, (SQLCHAR *)mtxt, buflen, &tlen, 0);
      } else {
          ret = SQL_ERROR;
      }
  
      if (SQL_SUCCEEDED(ret)) {
          if (Sqlstate)
              utf8_to_ucs2(qstr_ansi, -1, Sqlstate, 6);
          if (mtxt && tlen <= BufferLength) {
              // TODO (#612): Verify wide character conversion
              SQLULEN ulen = utf8_to_ucs2_lf(mtxt, tlen, FALSE, MessageText,
                                             BufferLength, TRUE);
              if (ulen == (SQLULEN)-1)
                  tlen = (SQLSMALLINT)locale_to_sqlwchar(
                      (SQLWCHAR *)MessageText, mtxt, BufferLength, FALSE);
              else
                  tlen = (SQLSMALLINT)ulen;
              if (tlen >= BufferLength)
                  ret = SQL_SUCCESS_WITH_INFO;
              else if (tlen < 0) {
                  char errc[32];
  
                  SPRINTF_FIXED(errc, "Error: SqlState=%s", qstr_ansi);
                  tlen = (SQLSMALLINT)utf8_to_ucs2(errc, -1, MessageText,
                                                   BufferLength);
              }
          }
          if (TextLength)
              *TextLength = tlen;
      }
      if (mtxt)
          free(mtxt);
      return ret;
  }
  