LCOV - code coverage report
Current view: top level - port - cpl_odbc.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 102 728 14.0 %
Date: 2024-11-21 22:18:42 Functions: 12 54 22.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OGR ODBC Driver
       4             :  * Purpose:  Declarations for ODBC Access Cover API.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2003, Frank Warmerdam
       9             :  * Copyright (c) 2008, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include <wchar.h>
      15             : 
      16             : #include "cpl_odbc.h"
      17             : #include "cpl_vsi.h"
      18             : #include "cpl_string.h"
      19             : #include "cpl_error.h"
      20             : 
      21             : #include <limits>
      22             : #include <mutex>
      23             : 
      24             : #ifndef SQLColumns_TABLE_CAT
      25             : #define SQLColumns_TABLE_CAT 1
      26             : #define SQLColumns_TABLE_SCHEM 2
      27             : #define SQLColumns_TABLE_NAME 3
      28             : #define SQLColumns_COLUMN_NAME 4
      29             : #define SQLColumns_DATA_TYPE 5
      30             : #define SQLColumns_TYPE_NAME 6
      31             : #define SQLColumns_COLUMN_SIZE 7
      32             : #define SQLColumns_BUFFER_LENGTH 8
      33             : #define SQLColumns_DECIMAL_DIGITS 9
      34             : #define SQLColumns_NUM_PREC_RADIX 10
      35             : #define SQLColumns_NULLABLE 11
      36             : #define SQLColumns_REMARKS 12
      37             : #define SQLColumns_COLUMN_DEF 13
      38             : #define SQLColumns_SQL_DATA_TYPE 14
      39             : #define SQLColumns_SQL_DATETIME_SUB 15
      40             : #define SQLColumns_CHAR_OCTET_LENGTH 16
      41             : #define SQLColumns_ORDINAL_POSITION 17
      42             : #define SQLColumns_IS_NULLABLE 18
      43             : #endif  // ndef SQLColumns_TABLE_CAT
      44             : 
      45             : /************************************************************************/
      46             : /*                           CPLODBCDriverInstaller()                   */
      47             : /************************************************************************/
      48             : 
      49           0 : CPLODBCDriverInstaller::CPLODBCDriverInstaller()
      50           0 :     : m_nErrorCode(0), m_nUsageCount(0)
      51             : {
      52           0 :     memset(m_szPathOut, '\0', ODBC_FILENAME_MAX);
      53           0 :     memset(m_szError, '\0', SQL_MAX_MESSAGE_LENGTH);
      54           0 : }
      55             : 
      56             : /************************************************************************/
      57             : /*                           InstallDriver()                            */
      58             : /************************************************************************/
      59             : 
      60           0 : int CPLODBCDriverInstaller::InstallDriver(const char *pszDriver,
      61             :                                           CPL_UNUSED const char *pszPathIn,
      62             :                                           WORD fRequest)
      63             : {
      64           0 :     CPLAssert(nullptr != pszDriver);
      65             : 
      66             :     // Try to install driver to system-wide location.
      67           0 :     if (FALSE == SQLInstallDriverEx(pszDriver, nullptr, m_szPathOut,
      68             :                                     ODBC_FILENAME_MAX, nullptr, fRequest,
      69             :                                     &m_nUsageCount))
      70             :     {
      71           0 :         const WORD nErrorNum = 1;  // TODO - a function param?
      72             : 
      73             :         // Failure is likely related to no write permissions to
      74             :         // system-wide default location, so try to install to HOME.
      75             : 
      76             :         static char *pszEnvIni = nullptr;
      77             : 
      78             :         // Read HOME location.
      79           0 :         const char *pszEnvHome = getenv("HOME");
      80           0 :         CPLAssert(nullptr != pszEnvHome);
      81           0 :         CPLDebug("ODBC", "HOME=%s", pszEnvHome);
      82             : 
      83           0 :         const char *pszEnvOdbcSysIni = nullptr;
      84           0 :         if (pszEnvIni == nullptr)
      85             :         {
      86             :             // record previous value, so we can rollback on failure
      87           0 :             pszEnvOdbcSysIni = getenv("ODBCSYSINI");
      88             : 
      89             :             // Set ODBCSYSINI variable pointing to HOME location.
      90           0 :             const size_t nLen = strlen(pszEnvHome) + 12;
      91           0 :             pszEnvIni = static_cast<char *>(CPLMalloc(nLen));
      92             : 
      93           0 :             snprintf(pszEnvIni, nLen, "ODBCSYSINI=%s", pszEnvHome);
      94             :             // A 'man putenv' shows that we cannot free pszEnvIni
      95             :             // because the pointer is used directly by putenv in old glibc.
      96             :             // coverity[tainted_string]
      97           0 :             putenv(pszEnvIni);
      98             : 
      99           0 :             CPLDebug("ODBC", "%s", pszEnvIni);
     100             :         }
     101             : 
     102             :         // Try to install ODBC driver in new location.
     103           0 :         if (FALSE == SQLInstallDriverEx(pszDriver, pszEnvHome, m_szPathOut,
     104             :                                         ODBC_FILENAME_MAX, nullptr, fRequest,
     105             :                                         &m_nUsageCount))
     106             :         {
     107             :             // if installing the driver fails, we need to roll back the changes
     108             :             // to ODBCSYSINI environment variable or all subsequent use of ODBC
     109             :             // calls will fail
     110           0 :             char *pszEnvRollback = nullptr;
     111           0 :             if (pszEnvOdbcSysIni)
     112             :             {
     113           0 :                 const size_t nLen = strlen(pszEnvOdbcSysIni) + 12;
     114           0 :                 pszEnvRollback = static_cast<char *>(CPLMalloc(nLen));
     115           0 :                 snprintf(pszEnvRollback, nLen, "ODBCSYSINI=%s",
     116             :                          pszEnvOdbcSysIni);
     117             :             }
     118             :             else
     119             :             {
     120             :                 // ODBCSYSINI not previously set, so remove
     121             : #ifdef _MSC_VER
     122             :                 // for MSVC an environment variable is removed by setting to
     123             :                 // empty string
     124             :                 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv?view=vs-2019
     125             :                 pszEnvRollback = CPLStrdup("ODBCSYSINI=");
     126             : #else
     127             :                 // for gnuc an environment variable is removed by not including
     128             :                 // the equal sign
     129             :                 // https://man7.org/linux/man-pages/man3/putenv.3.html
     130           0 :                 pszEnvRollback = CPLStrdup("ODBCSYSINI");
     131             : #endif
     132             :             }
     133             : 
     134             :             // A 'man putenv' shows that we cannot free pszEnvRollback
     135             :             // because the pointer is used directly by putenv in old glibc.
     136             :             // coverity[tainted_string]
     137           0 :             putenv(pszEnvRollback);
     138             : 
     139             :             CPL_UNUSED RETCODE cRet =
     140           0 :                 SQLInstallerError(nErrorNum, &m_nErrorCode, m_szError,
     141             :                                   SQL_MAX_MESSAGE_LENGTH, nullptr);
     142             :             (void)cRet;
     143           0 :             CPLAssert(SQL_SUCCESS == cRet || SQL_SUCCESS_WITH_INFO == cRet);
     144             : 
     145             :             // FAIL
     146           0 :             return FALSE;
     147             :         }
     148             :     }
     149             : 
     150             :     // SUCCESS
     151           0 :     return TRUE;
     152             : }
     153             : 
     154             : /************************************************************************/
     155             : /*                      FindMdbToolsDriverLib()                         */
     156             : /************************************************************************/
     157             : 
     158           1 : bool CPLODBCDriverInstaller::FindMdbToolsDriverLib(CPLString &osDriverFile)
     159             : {
     160           1 :     const char *pszDrvCfg = CPLGetConfigOption("MDBDRIVER_PATH", nullptr);
     161           1 :     if (nullptr != pszDrvCfg)
     162             :     {
     163             :         // Directory or file path
     164           0 :         CPLString strLibPath(pszDrvCfg);
     165             : 
     166             :         VSIStatBuf sStatBuf;
     167           0 :         if (VSIStat(pszDrvCfg, &sStatBuf) == 0 && VSI_ISDIR(sStatBuf.st_mode))
     168             :         {
     169             :             // Find default library in custom directory
     170             :             const char *pszDriverFile =
     171           0 :                 CPLFormFilename(pszDrvCfg, "libmdbodbc.so", nullptr);
     172           0 :             CPLAssert(nullptr != pszDriverFile);
     173             : 
     174           0 :             strLibPath = pszDriverFile;
     175             :         }
     176             : 
     177           0 :         if (LibraryExists(strLibPath.c_str()))
     178             :         {
     179             :             // Save custom driver path
     180           0 :             osDriverFile = std::move(strLibPath);
     181           0 :             return true;
     182             :         }
     183             :     }
     184             : 
     185             :     // Check if we have a declaration of the driver in /etc/odbcinst.ini
     186           1 :     GByte *pabyRet = nullptr;
     187           1 :     CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, "/etc/odbcinst.ini", &pabyRet,
     188             :                                      nullptr, 100 * 1000));
     189           1 :     if (pabyRet)
     190             :     {
     191           0 :         const bool bFound = strstr(reinterpret_cast<const char *>(pabyRet),
     192             :                                    "Microsoft Access Driver") != nullptr;
     193           0 :         CPLFree(pabyRet);
     194           0 :         if (bFound)
     195             :         {
     196           0 :             CPLDebug("ODBC", "Declaration of Microsoft Access Driver found in "
     197             :                              "/etc/odbcinst.ini");
     198           0 :             return false;
     199             :         }
     200             :     }
     201             : 
     202             :     // Default name and path of driver library
     203           1 :     const char *const apszLibNames[] = {
     204             :         "libmdbodbc.so", "libmdbodbc.so.0" /* for Ubuntu 8.04 support */
     205             :     };
     206           1 :     const char *const apzPaths[] = {
     207             :         "/usr/lib/x86_64-linux-gnu/odbc", /* ubuntu 20.04 */
     208             :         "/usr/lib64",
     209             :         "/usr/lib64/odbc", /* fedora */
     210             :         "/usr/local/lib64",
     211             :         "/usr/lib",
     212             :         "/usr/local/lib"};
     213             : 
     214             :     // Try to find library in default paths
     215           7 :     for (const char *pszPath : apzPaths)
     216             :     {
     217          18 :         for (const char *pszLibName : apszLibNames)
     218             :         {
     219             :             const char *pszDriverFile =
     220          12 :                 CPLFormFilename(pszPath, pszLibName, nullptr);
     221          12 :             CPLAssert(nullptr != pszDriverFile);
     222             : 
     223          12 :             if (LibraryExists(pszDriverFile))
     224             :             {
     225             :                 // Save default driver path
     226           0 :                 osDriverFile = pszDriverFile;
     227           0 :                 return true;
     228             :             }
     229             :         }
     230             :     }
     231             : 
     232           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     233             :              "ODBC: MDB Tools driver not found!\n");
     234             :     // Driver not found!
     235           1 :     return false;
     236             : }
     237             : 
     238             : /************************************************************************/
     239             : /*                              LibraryExists()                         */
     240             : /************************************************************************/
     241             : 
     242          12 : bool CPLODBCDriverInstaller::LibraryExists(const char *pszLibPath)
     243             : {
     244          12 :     CPLAssert(nullptr != pszLibPath);
     245             : 
     246             :     VSIStatBuf stb;
     247             : 
     248          12 :     if (0 == VSIStat(pszLibPath, &stb))
     249             :     {
     250           0 :         if (VSI_ISREG(stb.st_mode) || VSI_ISLNK(stb.st_mode))
     251             :         {
     252           0 :             return true;
     253             :         }
     254             :     }
     255             : 
     256          12 :     return false;
     257             : }
     258             : 
     259             : /************************************************************************/
     260             : /*                      InstallMdbToolsDriver()                         */
     261             : /************************************************************************/
     262             : 
     263           2 : void CPLODBCDriverInstaller::InstallMdbToolsDriver()
     264             : {
     265             : #ifdef _WIN32
     266             :     return;
     267             : #else
     268             :     static std::once_flag oofDriverInstallAttempted;
     269           2 :     std::call_once(
     270             :         oofDriverInstallAttempted,
     271           1 :         [=]
     272             :         {
     273             :             //
     274             :             // ODBCINST.INI NOTE:
     275             :             // This operation requires write access to odbcinst.ini file
     276             :             // located in directory pointed by ODBCINISYS variable.
     277             :             // Usually, it points to /etc, so non-root users can overwrite this
     278             :             // setting ODBCINISYS with location they have write access to, e.g.:
     279             :             // $ export ODBCINISYS=$HOME/etc
     280             :             // $ touch $ODBCINISYS/odbcinst.ini
     281             :             //
     282             :             // See: http://www.unixodbc.org/internals.html
     283             :             //
     284           2 :             CPLString osDriverFile;
     285             : 
     286           1 :             if (FindMdbToolsDriverLib(osDriverFile))
     287             :             {
     288           0 :                 CPLAssert(!osDriverFile.empty());
     289           0 :                 CPLDebug("ODBC", "MDB Tools driver: %s", osDriverFile.c_str());
     290             : 
     291           0 :                 std::string driver("Microsoft Access Driver (*.mdb)");
     292           0 :                 driver += '\0';
     293           0 :                 driver += "Driver=";
     294           0 :                 driver += osDriverFile;  // Found by FindDriverLib()
     295           0 :                 driver += '\0';
     296           0 :                 driver += "FileUsage=1";
     297           0 :                 driver += '\0';
     298           0 :                 driver += '\0';
     299             : 
     300             :                 // Rregister driver
     301           0 :                 CPLODBCDriverInstaller dri;
     302           0 :                 if (!dri.InstallDriver(driver.c_str(), nullptr,
     303             :                                        ODBC_INSTALL_COMPLETE))
     304             :                 {
     305             :                     // Report ODBC error
     306           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     307             :                              "ODBC: Unable to install MDB driver for ODBC, MDB "
     308             :                              "access may not supported: %s",
     309             :                              dri.GetLastError());
     310             :                 }
     311             :                 else
     312           0 :                     CPLDebug("ODBC",
     313             :                              "MDB Tools driver installed successfully!");
     314             :             }
     315           1 :         });
     316             : #endif
     317           2 : }
     318             : 
     319             : /************************************************************************/
     320             : /*                           RemoveDriver()                             */
     321             : /************************************************************************/
     322             : 
     323           0 : int CPLODBCDriverInstaller::RemoveDriver(const char *pszDriverName,
     324             :                                          int fRemoveDSN)
     325             : {
     326           0 :     CPLAssert(nullptr != pszDriverName);
     327             : 
     328           0 :     if (FALSE == SQLRemoveDriver(pszDriverName, fRemoveDSN, &m_nUsageCount))
     329             :     {
     330           0 :         const WORD nErrorNum = 1;  // TODO - a function param?
     331             : 
     332             :         // Retrieve error code and message.
     333           0 :         SQLInstallerError(nErrorNum, &m_nErrorCode, m_szError,
     334             :                           SQL_MAX_MESSAGE_LENGTH, nullptr);
     335             : 
     336           0 :         return FALSE;
     337             :     }
     338             : 
     339             :     // SUCCESS
     340           0 :     return TRUE;
     341             : }
     342             : 
     343             : /************************************************************************/
     344             : /*                           CPLODBCSession()                           */
     345             : /************************************************************************/
     346             : 
     347             : /** Constructor */
     348           3 : CPLODBCSession::CPLODBCSession()
     349             : {
     350           3 : }
     351             : 
     352             : /************************************************************************/
     353             : /*                          ~CPLODBCSession()                           */
     354             : /************************************************************************/
     355             : 
     356             : /** Destructor */
     357           3 : CPLODBCSession::~CPLODBCSession()
     358             : 
     359             : {
     360           3 :     CloseSession();
     361           3 : }
     362             : 
     363             : /************************************************************************/
     364             : /*                            CloseSession()                            */
     365             : /************************************************************************/
     366             : 
     367             : /** Close session */
     368          21 : int CPLODBCSession::CloseSession()
     369             : 
     370             : {
     371          21 :     if (m_hDBC != nullptr)
     372             :     {
     373           9 :         if (IsInTransaction())
     374           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     375             :                      "Closing session with active transactions.");
     376           9 :         CPLDebug("ODBC", "SQLDisconnect()");
     377           9 :         SQLDisconnect(m_hDBC);
     378           9 :         SQLFreeConnect(m_hDBC);
     379           9 :         m_hDBC = nullptr;
     380             :     }
     381             : 
     382          21 :     if (m_hEnv != nullptr)
     383             :     {
     384           9 :         SQLFreeEnv(m_hEnv);
     385           9 :         m_hEnv = nullptr;
     386             :     }
     387             : 
     388          21 :     return TRUE;
     389             : }
     390             : 
     391             : /************************************************************************/
     392             : /*                       ClearTransaction()                             */
     393             : /************************************************************************/
     394             : 
     395             : /** Clear transaction */
     396           0 : int CPLODBCSession::ClearTransaction()
     397             : 
     398             : {
     399             : #if (ODBCVER >= 0x0300)
     400             : 
     401           0 :     if (m_bAutoCommit)
     402           0 :         return TRUE;
     403             : 
     404             :     SQLUINTEGER bAutoCommit;
     405             :     // See if we already in manual commit mode.
     406           0 :     if (Failed(SQLGetConnectAttr(m_hDBC, SQL_ATTR_AUTOCOMMIT, &bAutoCommit,
     407           0 :                                  sizeof(SQLUINTEGER), nullptr)))
     408           0 :         return FALSE;
     409             : 
     410           0 :     if (bAutoCommit == SQL_AUTOCOMMIT_OFF)
     411             :     {
     412             :         // Switch the connection to auto commit mode (default).
     413           0 :         if (Failed(SQLSetConnectAttr(
     414             :                 m_hDBC, SQL_ATTR_AUTOCOMMIT,
     415           0 :                 reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_ON), 0)))
     416           0 :             return FALSE;
     417             :     }
     418             : 
     419           0 :     m_bInTransaction = FALSE;
     420           0 :     m_bAutoCommit = TRUE;
     421             : 
     422             : #endif
     423           0 :     return TRUE;
     424             : }
     425             : 
     426             : /************************************************************************/
     427             : /*                       CommitTransaction()                            */
     428             : /************************************************************************/
     429             : 
     430             : /** Begin transaction */
     431           0 : int CPLODBCSession::BeginTransaction()
     432             : 
     433             : {
     434             : #if (ODBCVER >= 0x0300)
     435             : 
     436             :     SQLUINTEGER bAutoCommit;
     437             :     // See if we already in manual commit mode.
     438           0 :     if (Failed(SQLGetConnectAttr(m_hDBC, SQL_ATTR_AUTOCOMMIT, &bAutoCommit,
     439           0 :                                  sizeof(SQLUINTEGER), nullptr)))
     440           0 :         return FALSE;
     441             : 
     442           0 :     if (bAutoCommit == SQL_AUTOCOMMIT_ON)
     443             :     {
     444             :         // Switch the connection to manual commit mode.
     445             : #ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
     446             : #pragma GCC diagnostic push
     447             : #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
     448             : #endif
     449           0 :         if (Failed(SQLSetConnectAttr(
     450             :                 m_hDBC, SQL_ATTR_AUTOCOMMIT,
     451           0 :                 reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0)))
     452           0 :             return FALSE;
     453             : #ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
     454             : #pragma GCC diagnostic pop
     455             : #endif
     456             :     }
     457             : 
     458           0 :     m_bInTransaction = TRUE;
     459           0 :     m_bAutoCommit = FALSE;
     460             : 
     461             : #endif
     462           0 :     return TRUE;
     463             : }
     464             : 
     465             : /************************************************************************/
     466             : /*                       CommitTransaction()                            */
     467             : /************************************************************************/
     468             : 
     469             : /** Commit transaction */
     470           0 : int CPLODBCSession::CommitTransaction()
     471             : 
     472             : {
     473             : #if (ODBCVER >= 0x0300)
     474             : 
     475           0 :     if (m_bInTransaction)
     476             :     {
     477           0 :         if (Failed(SQLEndTran(SQL_HANDLE_DBC, m_hDBC, SQL_COMMIT)))
     478             :         {
     479           0 :             return FALSE;
     480             :         }
     481           0 :         m_bInTransaction = FALSE;
     482             :     }
     483             : 
     484             : #endif
     485           0 :     return TRUE;
     486             : }
     487             : 
     488             : /************************************************************************/
     489             : /*                       RollbackTransaction()                          */
     490             : /************************************************************************/
     491             : 
     492             : /** Rollback transaction */
     493           0 : int CPLODBCSession::RollbackTransaction()
     494             : 
     495             : {
     496             : #if (ODBCVER >= 0x0300)
     497             : 
     498           0 :     if (m_bInTransaction)
     499             :     {
     500             :         // Rollback should not hide the previous error so Failed() is not
     501             :         // called.
     502           0 :         int nRetCode = SQLEndTran(SQL_HANDLE_DBC, m_hDBC, SQL_ROLLBACK);
     503           0 :         m_bInTransaction = FALSE;
     504             : 
     505           0 :         return (nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO);
     506             :     }
     507             : 
     508             : #endif
     509           0 :     return TRUE;
     510             : }
     511             : 
     512             : /************************************************************************/
     513             : /*                               Failed()                               */
     514             : /************************************************************************/
     515             : 
     516             : /** Test if a return code indicates failure, return TRUE if that
     517             :  * is the case. Also update error text.
     518             :  *
     519             :  * ODBC error messages are reported in the following format:
     520             :  * [SQLState]ErrorMessage(NativeErrorCode)
     521             :  *
     522             :  * Multiple error messages are delimited by ",".
     523             :  */
     524          27 : int CPLODBCSession::Failed(int nRetCode, HSTMT hStmt)
     525             : 
     526             : {
     527          27 :     m_osLastError.clear();
     528             : 
     529          27 :     if (nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO)
     530          18 :         return FALSE;
     531             : 
     532           9 :     SQLRETURN nDiagRetCode = SQL_SUCCESS;
     533          18 :     for (SQLSMALLINT nRecNum = 1; nDiagRetCode == SQL_SUCCESS; ++nRecNum)
     534             :     {
     535           9 :         SQLCHAR achSQLState[5 + 1] = {};
     536             :         SQLCHAR *pachCurErrMsg = static_cast<SQLCHAR *>(
     537           9 :             CPLMalloc((SQL_MAX_MESSAGE_LENGTH + 1) * sizeof(SQLCHAR)));
     538           9 :         SQLSMALLINT nTextLength = 0;
     539           9 :         SQLINTEGER nNativeError = 0;
     540             : 
     541           9 :         nDiagRetCode = SQLGetDiagRec(SQL_HANDLE_STMT, hStmt, nRecNum,
     542             :                                      achSQLState, &nNativeError,
     543             :                                      reinterpret_cast<SQLCHAR *>(pachCurErrMsg),
     544             :                                      SQL_MAX_MESSAGE_LENGTH, &nTextLength);
     545           9 :         if (nDiagRetCode == SQL_SUCCESS ||
     546             :             nDiagRetCode == SQL_SUCCESS_WITH_INFO)
     547             :         {
     548           0 :             if (nTextLength >= SQL_MAX_MESSAGE_LENGTH)
     549             :             {
     550             :                 // the buffer wasn't enough, retry
     551           0 :                 SQLSMALLINT nTextLength2 = 0;
     552           0 :                 pachCurErrMsg = static_cast<SQLCHAR *>(CPLRealloc(
     553           0 :                     pachCurErrMsg, (nTextLength + 1) * sizeof(SQLCHAR)));
     554           0 :                 nDiagRetCode = SQLGetDiagRec(
     555             :                     SQL_HANDLE_STMT, hStmt, nRecNum, achSQLState, &nNativeError,
     556             :                     reinterpret_cast<SQLCHAR *>(pachCurErrMsg), nTextLength,
     557             :                     &nTextLength2);
     558             :             }
     559           0 :             pachCurErrMsg[nTextLength] = '\0';
     560           0 :             m_osLastError += CPLString().Printf(
     561             :                 "%s[%5s]%s(" CPL_FRMT_GIB ")",
     562           0 :                 (m_osLastError.empty() ? "" : ", "), achSQLState, pachCurErrMsg,
     563           0 :                 static_cast<GIntBig>(nNativeError));
     564             :         }
     565           9 :         CPLFree(pachCurErrMsg);
     566             :     }
     567             : 
     568           9 :     if (nRetCode == SQL_ERROR && m_bInTransaction)
     569           0 :         RollbackTransaction();
     570             : 
     571           9 :     return TRUE;
     572             : }
     573             : 
     574             : /************************************************************************/
     575             : /*                          ConnectToMsAccess()                          */
     576             : /************************************************************************/
     577             : 
     578             : /**
     579             :  * Connects to a Microsoft Access database.
     580             :  *
     581             :  * @param pszName The file name of the Access database to connect to.  This is
     582             :  * not optional.
     583             :  *
     584             :  * @param pszDSNStringTemplate optional DSN string template for Microsoft Access
     585             :  * ODBC Driver. If not specified, then a set of known driver templates will
     586             :  * be used automatically as a fallback. If specified, it is the caller's
     587             :  * responsibility to ensure that the template is correctly formatted.
     588             :  *
     589             :  * @return TRUE on success or FALSE on failure. Errors will automatically be
     590             :  * reported via CPLError.
     591             :  *
     592             :  * @since GDAL 3.2
     593             :  */
     594           2 : bool CPLODBCSession::ConnectToMsAccess(const char *pszName,
     595             :                                        const char *pszDSNStringTemplate)
     596             : {
     597             :     const auto Connect =
     598          24 :         [this, &pszName](const char *l_pszDSNStringTemplate, bool bVerboseError)
     599             :     {
     600          16 :         std::string osDSN;
     601           8 :         constexpr const char *PCT_S = "%s";
     602           8 :         const char *pszPctS = strstr(l_pszDSNStringTemplate, PCT_S);
     603           8 :         if (!pszPctS)
     604             :         {
     605           0 :             osDSN = l_pszDSNStringTemplate;
     606             :         }
     607             :         else
     608             :         {
     609             :             osDSN.assign(l_pszDSNStringTemplate,
     610           8 :                          pszPctS - l_pszDSNStringTemplate);
     611           8 :             osDSN += pszName;
     612           8 :             osDSN += (pszPctS + strlen(PCT_S));
     613             :         }
     614           8 :         CPLDebug("ODBC", "EstablishSession(%s)", osDSN.c_str());
     615           8 :         int bError = !EstablishSession(osDSN.c_str(), nullptr, nullptr);
     616           8 :         if (bError)
     617             :         {
     618           8 :             if (bVerboseError)
     619             :             {
     620           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     621             :                          "Unable to initialize ODBC connection to DSN for %s,\n"
     622             :                          "%s",
     623             :                          osDSN.c_str(), GetLastError());
     624             :             }
     625           8 :             return false;
     626             :         }
     627             : 
     628           0 :         return true;
     629           2 :     };
     630             : 
     631           2 :     if (pszDSNStringTemplate)
     632             :     {
     633           0 :         return Connect(pszDSNStringTemplate, true);
     634             :     }
     635             : 
     636           8 :     for (const char *l_pszDSNStringTemplate :
     637             :          {"DRIVER=Microsoft Access Driver (*.mdb, *.accdb);DBQ=%s",
     638             :           "DRIVER=Microsoft Access Driver (*.mdb, *.accdb);DBQ=\"%s\"",
     639             :           "DRIVER=Microsoft Access Driver (*.mdb);DBQ=%s",
     640          10 :           "DRIVER=Microsoft Access Driver (*.mdb);DBQ=\"%s\""})
     641             :     {
     642           8 :         if (Connect(l_pszDSNStringTemplate, false))
     643           0 :             return true;
     644             :     }
     645             : 
     646           2 :     CPLError(CE_Failure, CPLE_AppDefined,
     647             :              "Unable to initialize ODBC connection to DSN for %s,\n"
     648             :              "%s",
     649             :              pszName, GetLastError());
     650           2 :     return false;
     651             : }
     652             : 
     653             : /************************************************************************/
     654             : /*                          EstablishSession()                          */
     655             : /************************************************************************/
     656             : 
     657             : /**
     658             :  * Connect to database and logon.
     659             :  *
     660             :  * @param pszDSN The name of the DSN being used to connect.  This is not
     661             :  * optional.
     662             :  *
     663             :  * @param pszUserid the userid to logon as, may be NULL if not not required,
     664             :  * or provided by the DSN.
     665             :  *
     666             :  * @param pszPassword the password to logon with.   May be NULL if not required
     667             :  * or provided by the DSN.
     668             :  *
     669             :  * @return TRUE on success or FALSE on failure.  Call GetLastError() to get
     670             :  * details on failure.
     671             :  */
     672             : 
     673           9 : int CPLODBCSession::EstablishSession(const char *pszDSN, const char *pszUserid,
     674             :                                      const char *pszPassword)
     675             : 
     676             : {
     677           9 :     CloseSession();
     678             : 
     679           9 :     if (Failed(SQLAllocEnv(&m_hEnv)))
     680           0 :         return FALSE;
     681             : 
     682           9 :     if (Failed(SQLAllocConnect(m_hEnv, &m_hDBC)))
     683             :     {
     684           0 :         CloseSession();
     685           0 :         return FALSE;
     686             :     }
     687             : 
     688             : #ifdef _MSC_VER
     689             : #pragma warning(push)
     690             : // warning C4996: 'SQLSetConnectOption': ODBC API: SQLSetConnectOption is
     691             : // deprecated. Please use SQLSetConnectAttr instead
     692             : #pragma warning(disable : 4996)
     693             : #endif
     694           9 :     SQLSetConnectOption(m_hDBC, SQL_LOGIN_TIMEOUT, 30);
     695             : #ifdef _MSC_VER
     696             : #pragma warning(pop)
     697             : #endif
     698             : 
     699           9 :     if (pszUserid == nullptr)
     700           8 :         pszUserid = "";
     701           9 :     if (pszPassword == nullptr)
     702           8 :         pszPassword = "";
     703             : 
     704          18 :     std::string osDSN(pszDSN);
     705             : #if defined(_WIN32)
     706             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
     707             :     {
     708             :         char *pszTemp = CPLRecode(pszDSN, CPL_ENC_UTF8, "CP_ACP");
     709             :         osDSN = pszTemp;
     710             :         CPLFree(pszTemp);
     711             :     }
     712             : #endif
     713             : 
     714           9 :     bool bFailed = false;
     715           9 :     if (strstr(pszDSN, "=") != nullptr)
     716             :     {
     717           9 :         CPLDebug("ODBC", "SQLDriverConnect(%s)", pszDSN);
     718           9 :         SQLCHAR szOutConnString[1024] = {};
     719           9 :         SQLSMALLINT nOutConnStringLen = 0;
     720             : 
     721           9 :         bFailed = CPL_TO_BOOL(Failed(SQLDriverConnect(
     722             :             m_hDBC, nullptr,
     723           9 :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(osDSN.c_str())),
     724           9 :             static_cast<SQLSMALLINT>(strlen(pszDSN)), szOutConnString,
     725             :             sizeof(szOutConnString), &nOutConnStringLen, SQL_DRIVER_NOPROMPT)));
     726             :     }
     727             :     else
     728             :     {
     729           0 :         CPLDebug("ODBC", "SQLConnect(%s)", pszDSN);
     730           0 :         bFailed = CPL_TO_BOOL(Failed(SQLConnect(
     731             :             m_hDBC,
     732           0 :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(osDSN.c_str())),
     733             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszUserid)),
     734             :             SQL_NTS,
     735             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszPassword)),
     736             :             SQL_NTS)));
     737             :     }
     738             : 
     739           9 :     if (bFailed)
     740             :     {
     741           9 :         CPLDebug("ODBC", "... failed: %s", GetLastError());
     742           9 :         CloseSession();
     743           9 :         return FALSE;
     744             :     }
     745             : 
     746           0 :     return TRUE;
     747             : }
     748             : 
     749             : /************************************************************************/
     750             : /*                            GetLastError()                            */
     751             : /************************************************************************/
     752             : 
     753             : /**
     754             :  * Returns the last ODBC error message.
     755             :  *
     756             :  * @return pointer to an internal buffer with the error message in it.
     757             :  * Do not free or alter.  Will be an empty (but not NULL) string if there is
     758             :  * no pending error info.
     759             :  */
     760             : 
     761          12 : const char *CPLODBCSession::GetLastError()
     762             : 
     763             : {
     764          12 :     return m_osLastError.c_str();
     765             : }
     766             : 
     767             : /************************************************************************/
     768             : /* ==================================================================== */
     769             : /*                           CPLODBCStatement                           */
     770             : /* ==================================================================== */
     771             : /************************************************************************/
     772             : 
     773             : /************************************************************************/
     774             : /*                          CPLODBCStatement()                          */
     775             : /************************************************************************/
     776             : 
     777             : /**
     778             :  * Constructor.
     779             :  *
     780             :  * The optional flags argument can be used to specify flags which control
     781             :  * the behavior of the statement.
     782             :  */
     783           0 : CPLODBCStatement::CPLODBCStatement(CPLODBCSession *poSession, const int flags)
     784           0 :     : m_nFlags(flags), m_poSession(poSession)
     785             : {
     786             : 
     787           0 :     if (Failed(SQLAllocStmt(poSession->GetConnection(), &m_hStmt)))
     788             :     {
     789           0 :         m_hStmt = nullptr;
     790             :     }
     791           0 : }
     792             : 
     793             : /************************************************************************/
     794             : /*                         ~CPLODBCStatement()                          */
     795             : /************************************************************************/
     796             : 
     797             : /** Destructor */
     798           0 : CPLODBCStatement::~CPLODBCStatement()
     799             : 
     800             : {
     801           0 :     Clear();
     802             : 
     803           0 :     if (m_hStmt != nullptr)
     804           0 :         SQLFreeStmt(m_hStmt, SQL_DROP);
     805           0 : }
     806             : 
     807             : /************************************************************************/
     808             : /*                             ExecuteSQL()                             */
     809             : /************************************************************************/
     810             : 
     811             : /**
     812             :  * Execute an SQL statement.
     813             :  *
     814             :  * This method will execute the passed (or stored) SQL statement,
     815             :  * and initialize information about the resultset if there is one.
     816             :  * If a NULL statement is passed, the internal stored statement that
     817             :  * has been previously set via Append() or Appendf() calls will be used.
     818             :  *
     819             :  * @param pszStatement the SQL statement to execute, or NULL if the
     820             :  * internally saved one should be used.
     821             :  *
     822             :  * @return TRUE on success or FALSE if there is an error.  Error details
     823             :  * can be fetched with OGRODBCSession::GetLastError().
     824             :  */
     825             : 
     826           0 : int CPLODBCStatement::ExecuteSQL(const char *pszStatement)
     827             : 
     828             : {
     829           0 :     if (m_poSession == nullptr || m_hStmt == nullptr)
     830             :     {
     831             :         // We should post an error.
     832           0 :         return FALSE;
     833             :     }
     834             : 
     835           0 :     if (pszStatement != nullptr)
     836             :     {
     837           0 :         Clear();
     838           0 :         Append(pszStatement);
     839             :     }
     840             : 
     841             : #if (ODBCVER >= 0x0300)
     842             : 
     843           0 :     if (!m_poSession->IsInTransaction())
     844             :     {
     845             :         // Commit pending transactions and set to autocommit mode.
     846           0 :         m_poSession->ClearTransaction();
     847             :     }
     848             : 
     849             : #endif
     850             : 
     851             :     // SQL_NTS=-3 is a valid value for SQLExecDirect.
     852             :     // coverity[negative_returns]
     853           0 :     if (Failed(SQLExecDirect(
     854           0 :             m_hStmt, reinterpret_cast<SQLCHAR *>(m_pszStatement), SQL_NTS)))
     855           0 :         return FALSE;
     856             : 
     857           0 :     return CollectResultsInfo();
     858             : }
     859             : 
     860             : /************************************************************************/
     861             : /*                         CollectResultsInfo()                         */
     862             : /************************************************************************/
     863             : 
     864             : /** CollectResultsInfo */
     865           0 : int CPLODBCStatement::CollectResultsInfo()
     866             : 
     867             : {
     868           0 :     if (m_poSession == nullptr || m_hStmt == nullptr)
     869             :     {
     870             :         // We should post an error.
     871           0 :         return FALSE;
     872             :     }
     873             : 
     874           0 :     if (Failed(SQLNumResultCols(m_hStmt, &m_nColCount)))
     875           0 :         return FALSE;
     876             : 
     877             :     /* -------------------------------------------------------------------- */
     878             :     /*      Allocate per column information.                                */
     879             :     /* -------------------------------------------------------------------- */
     880           0 :     m_papszColNames =
     881           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     882           0 :     m_papszColValues =
     883           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     884           0 :     m_panColValueLengths = static_cast<CPL_SQLLEN *>(
     885           0 :         CPLCalloc(sizeof(CPL_SQLLEN), m_nColCount + 1));
     886           0 :     if (m_nFlags & Flag::RetrieveNumericColumnsAsDouble)
     887             :     {
     888           0 :         m_padColValuesAsDouble =
     889           0 :             static_cast<double *>(CPLCalloc(sizeof(double), m_nColCount + 1));
     890             :     }
     891             :     else
     892             :     {
     893           0 :         m_padColValuesAsDouble = nullptr;
     894             :     }
     895             : 
     896           0 :     m_panColType =
     897           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     898           0 :     m_papszColTypeNames =
     899           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     900           0 :     m_panColSize =
     901           0 :         static_cast<CPL_SQLULEN *>(CPLCalloc(sizeof(CPL_SQLULEN), m_nColCount));
     902           0 :     m_panColPrecision =
     903           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     904           0 :     m_panColNullable =
     905           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     906           0 :     m_papszColColumnDef =
     907           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     908             : 
     909             :     /* -------------------------------------------------------------------- */
     910             :     /*      Fetch column descriptions.                                      */
     911             :     /* -------------------------------------------------------------------- */
     912           0 :     for (SQLUSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
     913             :     {
     914           0 :         SQLCHAR szName[256] = {};
     915           0 :         SQLSMALLINT nNameLength = 0;
     916             : 
     917           0 :         if (Failed(SQLDescribeCol(m_hStmt, iCol + 1, szName, sizeof(szName),
     918           0 :                                   &nNameLength, m_panColType + iCol,
     919           0 :                                   m_panColSize + iCol, m_panColPrecision + iCol,
     920           0 :                                   m_panColNullable + iCol)))
     921           0 :             return FALSE;
     922             : 
     923           0 :         szName[nNameLength] = '\0';  // Paranoid; the string should be
     924             :                                      // null-terminated by the driver.
     925           0 :         m_papszColNames[iCol] = CPLStrdup(reinterpret_cast<char *>(szName));
     926             : 
     927             :         // SQLDescribeCol() fetches just a subset of column attributes.
     928             :         // In addition to above data we need data type name.
     929           0 :         if (Failed(SQLColAttribute(m_hStmt, iCol + 1, SQL_DESC_TYPE_NAME,
     930             :                                    szName, sizeof(szName), &nNameLength,
     931           0 :                                    nullptr)))
     932           0 :             return FALSE;
     933             : 
     934           0 :         szName[nNameLength] = '\0';  // Paranoid.
     935           0 :         m_papszColTypeNames[iCol] = CPLStrdup(reinterpret_cast<char *>(szName));
     936             : 
     937             : #if DEBUG_VERBOSE
     938             :         CPLDebug("ODBC", "%s %s %d", m_papszColNames[iCol], szName,
     939             :                  m_panColType[iCol]);
     940             : #endif
     941             :     }
     942             : 
     943           0 :     return TRUE;
     944             : }
     945             : 
     946             : /************************************************************************/
     947             : /*                            GetRowCountAffected()                     */
     948             : /************************************************************************/
     949             : 
     950             : /** GetRowCountAffected */
     951           0 : int CPLODBCStatement::GetRowCountAffected()
     952             : {
     953           0 :     SQLLEN nResultCount = 0;
     954           0 :     SQLRowCount(m_hStmt, &nResultCount);
     955             : 
     956           0 :     return static_cast<int>(nResultCount);
     957             : }
     958             : 
     959             : /************************************************************************/
     960             : /*                            GetColCount()                             */
     961             : /************************************************************************/
     962             : 
     963             : /**
     964             :  * Fetch the resultset column count.
     965             :  *
     966             :  * @return the column count, or zero if there is no resultset.
     967             :  */
     968             : 
     969           0 : int CPLODBCStatement::GetColCount()
     970             : 
     971             : {
     972           0 :     return m_nColCount;
     973             : }
     974             : 
     975             : /************************************************************************/
     976             : /*                             GetColName()                             */
     977             : /************************************************************************/
     978             : 
     979             : /**
     980             :  * Fetch a column name.
     981             :  *
     982             :  * @param iCol the zero based column index.
     983             :  *
     984             :  * @return NULL on failure (out of bounds column), or a pointer to an
     985             :  * internal copy of the column name.
     986             :  */
     987             : 
     988           0 : const char *CPLODBCStatement::GetColName(int iCol)
     989             : 
     990             : {
     991           0 :     if (iCol < 0 || iCol >= m_nColCount)
     992           0 :         return nullptr;
     993             : 
     994           0 :     return m_papszColNames[iCol];
     995             : }
     996             : 
     997             : /************************************************************************/
     998             : /*                             GetColType()                             */
     999             : /************************************************************************/
    1000             : 
    1001             : /**
    1002             :  * Fetch a column data type.
    1003             :  *
    1004             :  * The return type code is a an ODBC SQL_ code, one of SQL_UNKNOWN_TYPE,
    1005             :  * SQL_CHAR, SQL_NUMERIC, SQL_DECIMAL, SQL_INTEGER, SQL_SMALLINT, SQL_FLOAT,
    1006             :  * SQL_REAL, SQL_DOUBLE, SQL_DATETIME, SQL_VARCHAR, SQL_TYPE_DATE,
    1007             :  * SQL_TYPE_TIME, SQL_TYPE_TIMESTAMPT.
    1008             :  *
    1009             :  * @param iCol the zero based column index.
    1010             :  *
    1011             :  * @return type code or -1 if the column is illegal.
    1012             :  */
    1013             : 
    1014           0 : short CPLODBCStatement::GetColType(int iCol)
    1015             : 
    1016             : {
    1017           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1018           0 :         return -1;
    1019             : 
    1020           0 :     return m_panColType[iCol];
    1021             : }
    1022             : 
    1023             : /************************************************************************/
    1024             : /*                             GetColTypeName()                         */
    1025             : /************************************************************************/
    1026             : 
    1027             : /**
    1028             :  * Fetch a column data type name.
    1029             :  *
    1030             :  * Returns data source-dependent data type name; for example, "CHAR",
    1031             :  * "VARCHAR", "MONEY", "LONG VARBINAR", or "CHAR ( ) FOR BIT DATA".
    1032             :  *
    1033             :  * @param iCol the zero based column index.
    1034             :  *
    1035             :  * @return NULL on failure (out of bounds column), or a pointer to an
    1036             :  * internal copy of the column dat type name.
    1037             :  */
    1038             : 
    1039           0 : const char *CPLODBCStatement::GetColTypeName(int iCol)
    1040             : 
    1041             : {
    1042           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1043           0 :         return nullptr;
    1044             : 
    1045           0 :     return m_papszColTypeNames[iCol];
    1046             : }
    1047             : 
    1048             : /************************************************************************/
    1049             : /*                             GetColSize()                             */
    1050             : /************************************************************************/
    1051             : 
    1052             : /**
    1053             :  * Fetch the column width.
    1054             :  *
    1055             :  * @param iCol the zero based column index.
    1056             :  *
    1057             :  * @return column width, zero for unknown width columns.
    1058             :  */
    1059             : 
    1060           0 : short CPLODBCStatement::GetColSize(int iCol)
    1061             : 
    1062             : {
    1063           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1064           0 :         return -1;
    1065             : 
    1066           0 :     return static_cast<short>(m_panColSize[iCol]);
    1067             : }
    1068             : 
    1069             : /************************************************************************/
    1070             : /*                          GetColPrecision()                           */
    1071             : /************************************************************************/
    1072             : 
    1073             : /**
    1074             :  * Fetch the column precision.
    1075             :  *
    1076             :  * @param iCol the zero based column index.
    1077             :  *
    1078             :  * @return column precision, may be zero or the same as column size for
    1079             :  * columns to which it does not apply.
    1080             :  */
    1081             : 
    1082           0 : short CPLODBCStatement::GetColPrecision(int iCol)
    1083             : 
    1084             : {
    1085           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1086           0 :         return -1;
    1087             : 
    1088           0 :     return m_panColPrecision[iCol];
    1089             : }
    1090             : 
    1091             : /************************************************************************/
    1092             : /*                           GetColNullable()                           */
    1093             : /************************************************************************/
    1094             : 
    1095             : /**
    1096             :  * Fetch the column nullability.
    1097             :  *
    1098             :  * @param iCol the zero based column index.
    1099             :  *
    1100             :  * @return TRUE if the column may contains or FALSE otherwise.
    1101             :  */
    1102             : 
    1103           0 : short CPLODBCStatement::GetColNullable(int iCol)
    1104             : 
    1105             : {
    1106           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1107           0 :         return -1;
    1108             : 
    1109           0 :     return m_panColNullable[iCol];
    1110             : }
    1111             : 
    1112             : /************************************************************************/
    1113             : /*                             GetColColumnDef()                        */
    1114             : /************************************************************************/
    1115             : 
    1116             : /**
    1117             :  * Fetch a column default value.
    1118             :  *
    1119             :  * Returns the default value of a column.
    1120             :  *
    1121             :  * @param iCol the zero based column index.
    1122             :  *
    1123             :  * @return NULL if the default value is not dpecified
    1124             :  * or the internal copy of the default value.
    1125             :  */
    1126             : 
    1127           0 : const char *CPLODBCStatement::GetColColumnDef(int iCol)
    1128             : 
    1129             : {
    1130           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1131           0 :         return nullptr;
    1132             : 
    1133           0 :     return m_papszColColumnDef[iCol];
    1134             : }
    1135             : 
    1136             : /************************************************************************/
    1137             : /*                               Fetch()                                */
    1138             : /************************************************************************/
    1139             : 
    1140             : /**
    1141             :  * Fetch a new record.
    1142             :  *
    1143             :  * Requests the next row in the current resultset using the SQLFetchScroll()
    1144             :  * call.  Note that many ODBC drivers only support the default forward
    1145             :  * fetching one record at a time.  Only SQL_FETCH_NEXT (the default) should
    1146             :  * be considered reliable on all drivers.
    1147             :  *
    1148             :  * Currently it isn't clear how to determine whether an error or a normal
    1149             :  * out of data condition has occurred if Fetch() fails.
    1150             :  *
    1151             :  * @param nOrientation One of SQL_FETCH_NEXT, SQL_FETCH_LAST, SQL_FETCH_PRIOR,
    1152             :  * SQL_FETCH_ABSOLUTE, or SQL_FETCH_RELATIVE (default is SQL_FETCH_NEXT).
    1153             :  *
    1154             :  * @param nOffset the offset (number of records), ignored for some
    1155             :  * orientations.
    1156             :  *
    1157             :  * @return TRUE if a new row is successfully fetched, or FALSE if not.
    1158             :  */
    1159             : 
    1160           0 : int CPLODBCStatement::Fetch(int nOrientation, int nOffset)
    1161             : 
    1162             : {
    1163           0 :     ClearColumnData();
    1164             : 
    1165           0 :     if (m_hStmt == nullptr || m_nColCount < 1)
    1166           0 :         return FALSE;
    1167             : 
    1168             :     /* -------------------------------------------------------------------- */
    1169             :     /*      Fetch a new row.  Note that some brain dead drives (such as     */
    1170             :     /*      the unixodbc text file driver) don't implement                  */
    1171             :     /*      SQLScrollFetch(), so we try to stick to SQLFetch() if we        */
    1172             :     /*      can).                                                           */
    1173             :     /* -------------------------------------------------------------------- */
    1174             :     SQLRETURN nRetCode;
    1175             : 
    1176           0 :     if (nOrientation == SQL_FETCH_NEXT && nOffset == 0)
    1177             :     {
    1178           0 :         nRetCode = SQLFetch(m_hStmt);
    1179             :     }
    1180             :     else
    1181             :     {
    1182           0 :         nRetCode = SQLFetchScroll(
    1183             :             m_hStmt, static_cast<SQLSMALLINT>(nOrientation), nOffset);
    1184             :     }
    1185           0 :     if (Failed(nRetCode))
    1186             :     {
    1187           0 :         if (nRetCode != SQL_NO_DATA)
    1188             :         {
    1189           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1190           0 :                      m_poSession->GetLastError());
    1191             :         }
    1192           0 :         return FALSE;
    1193             :     }
    1194             : 
    1195             :     /* -------------------------------------------------------------------- */
    1196             :     /*      Pull out all the column values.                                 */
    1197             :     /* -------------------------------------------------------------------- */
    1198           0 :     for (SQLSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1199             :     {
    1200           0 :         CPL_SQLLEN cbDataLen = 0;
    1201           0 :         if (m_padColValuesAsDouble)
    1202           0 :             m_padColValuesAsDouble[iCol] =
    1203           0 :                 std::numeric_limits<double>::quiet_NaN();
    1204           0 :         SQLSMALLINT nFetchType = GetTypeMapping(m_panColType[iCol]);
    1205             : 
    1206             :         // Handle values other than WCHAR and BINARY as CHAR.
    1207           0 :         if (nFetchType != SQL_C_BINARY && nFetchType != SQL_C_WCHAR)
    1208           0 :             nFetchType = SQL_C_CHAR;
    1209             : 
    1210           0 :         char szWrkData[513] = {};
    1211             : 
    1212             :         // If RetrieveNumericColumnsAsDouble flag is set, then read numeric
    1213             :         // columns using numeric data types and populate native double column
    1214             :         // values array. This allows retrieval of the original numeric value as
    1215             :         // a double via GetColDataAsDouble without risk of loss of precision.
    1216             :         // Additionally, some ODBC drivers (e.g. the MS Access ODBC driver)
    1217             :         // require reading numeric values using numeric data types, otherwise
    1218             :         // incorrect values can result. See
    1219             :         // https://github.com/OSGeo/gdal/issues/3885
    1220           0 :         if (m_padColValuesAsDouble &&
    1221           0 :             (m_panColType[iCol] == SQL_DOUBLE ||
    1222           0 :              m_panColType[iCol] == SQL_FLOAT || m_panColType[iCol] == SQL_REAL))
    1223             :         {
    1224           0 :             if (m_panColType[iCol] == SQL_DOUBLE)
    1225             :             {
    1226           0 :                 double dfValue = 0;
    1227           0 :                 nRetCode = SQLGetData(m_hStmt, iCol + 1, SQL_C_DOUBLE, &dfValue,
    1228             :                                       sizeof(dfValue), &cbDataLen);
    1229           0 :                 if (cbDataLen != SQL_NULL_DATA)
    1230           0 :                     m_padColValuesAsDouble[iCol] = dfValue;
    1231             :             }
    1232             :             else
    1233             :             {
    1234             :                 // note -- cannot request a float column as SQL_C_DOUBLE when
    1235             :                 // using mdbtools driver!
    1236           0 :                 float fValue = 0;
    1237           0 :                 nRetCode = SQLGetData(m_hStmt, iCol + 1, SQL_C_FLOAT, &fValue,
    1238             :                                       sizeof(fValue), &cbDataLen);
    1239           0 :                 if (cbDataLen != SQL_NULL_DATA)
    1240           0 :                     m_padColValuesAsDouble[iCol] = static_cast<double>(fValue);
    1241             :             }
    1242           0 :             if (nRetCode != SQL_NO_DATA && Failed(nRetCode))
    1243             :             {
    1244           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1245           0 :                          m_poSession->GetLastError());
    1246           0 :                 return FALSE;
    1247             :             }
    1248             :             else
    1249             :             {
    1250           0 :                 m_papszColValues[iCol] = nullptr;
    1251           0 :                 m_panColValueLengths[iCol] = 0;
    1252           0 :                 continue;
    1253             :             }
    1254             :         }
    1255             : 
    1256           0 :         nRetCode = SQLGetData(m_hStmt, iCol + 1, nFetchType, szWrkData,
    1257             :                               sizeof(szWrkData) - 1, &cbDataLen);
    1258             : 
    1259             :         // SQLGetData() is giving garbage values in the first 4 bytes of
    1260             :         // cbDataLen in some architectures. Converting it to int discards the
    1261             :         // unnecessary bytes. This should not be a problem unless the buffer
    1262             :         // size reaches 2GB. (#3385)
    1263           0 :         cbDataLen = static_cast<int>(cbDataLen);
    1264             : 
    1265             :         // a return code of SQL_NO_DATA is not indicative of an error - see
    1266             :         // https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/getting-long-data?view=sql-server-ver15
    1267             :         // "When there is no more data to return, SQLGetData returns
    1268             :         // SQL_NO_DATA" and the example from that page which uses: while ((rc =
    1269             :         // SQLGetData(hstmt, 2, SQL_C_BINARY, BinaryPtr, sizeof(BinaryPtr),
    1270             :         // &BinaryLenOrInd)) != SQL_NO_DATA) { ... }
    1271           0 :         if (nRetCode != SQL_NO_DATA && Failed(nRetCode))
    1272             :         {
    1273           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1274           0 :                      m_poSession->GetLastError());
    1275           0 :             return FALSE;
    1276             :         }
    1277             : 
    1278             :         // if first call to SQLGetData resulted in SQL_NO_DATA return code, then
    1279             :         // the data is empty (NULL)
    1280           0 :         if (cbDataLen == SQL_NULL_DATA || nRetCode == SQL_NO_DATA)
    1281             :         {
    1282           0 :             m_papszColValues[iCol] = nullptr;
    1283           0 :             m_panColValueLengths[iCol] = 0;
    1284             :         }
    1285             : 
    1286             :         // Assume big result: should check for state=SQLSATE 01004.
    1287           0 :         else if (nRetCode == SQL_SUCCESS_WITH_INFO)
    1288             :         {
    1289           0 :             if (cbDataLen >= static_cast<CPL_SQLLEN>(sizeof(szWrkData) - 1) ||
    1290           0 :                 cbDataLen == SQL_NO_TOTAL)
    1291             :             {
    1292           0 :                 cbDataLen = static_cast<CPL_SQLLEN>(sizeof(szWrkData) - 1);
    1293           0 :                 if (nFetchType == SQL_C_CHAR)
    1294           0 :                     while ((cbDataLen > 1) && (szWrkData[cbDataLen - 1] == 0))
    1295           0 :                         --cbDataLen;  // Trimming the extra terminators: bug 990
    1296           0 :                 else if (nFetchType == SQL_C_WCHAR)
    1297           0 :                     while ((cbDataLen > 1) && (szWrkData[cbDataLen - 1] == 0) &&
    1298           0 :                            (szWrkData[cbDataLen - 2] == 0))
    1299           0 :                         cbDataLen -= 2;  // Trimming the extra terminators.
    1300             :             }
    1301             : 
    1302           0 :             m_papszColValues[iCol] =
    1303           0 :                 static_cast<char *>(CPLMalloc(cbDataLen + 2));
    1304           0 :             memcpy(m_papszColValues[iCol], szWrkData, cbDataLen);
    1305           0 :             m_papszColValues[iCol][cbDataLen] = '\0';
    1306           0 :             m_papszColValues[iCol][cbDataLen + 1] = '\0';
    1307           0 :             m_panColValueLengths[iCol] = cbDataLen;
    1308             : 
    1309             :             while (true)
    1310             :             {
    1311           0 :                 nRetCode = SQLGetData(
    1312           0 :                     m_hStmt, static_cast<SQLUSMALLINT>(iCol) + 1, nFetchType,
    1313             :                     szWrkData, sizeof(szWrkData) - 1, &cbDataLen);
    1314           0 :                 if (nRetCode == SQL_NO_DATA)
    1315           0 :                     break;
    1316             : 
    1317           0 :                 if (Failed(nRetCode))
    1318             :                 {
    1319           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1320           0 :                              m_poSession->GetLastError());
    1321           0 :                     return FALSE;
    1322             :                 }
    1323             : 
    1324             :                 CPL_SQLLEN nChunkLen;
    1325           0 :                 if (cbDataLen >= static_cast<int>(sizeof(szWrkData) - 1) ||
    1326           0 :                     cbDataLen == SQL_NO_TOTAL)
    1327             :                 {
    1328           0 :                     nChunkLen = sizeof(szWrkData) - 1;
    1329           0 :                     if (nFetchType == SQL_C_CHAR)
    1330           0 :                         while ((nChunkLen > 1) &&
    1331           0 :                                (szWrkData[nChunkLen - 1] == 0))
    1332           0 :                             --nChunkLen;  // Trimming the extra terminators.
    1333           0 :                     else if (nFetchType == SQL_C_WCHAR)
    1334           0 :                         while ((nChunkLen > 1) &&
    1335           0 :                                (szWrkData[nChunkLen - 1] == 0) &&
    1336           0 :                                (szWrkData[nChunkLen - 2] == 0))
    1337           0 :                             nChunkLen -= 2;  // Trimming the extra terminators.
    1338             :                 }
    1339             :                 else
    1340             :                 {
    1341           0 :                     nChunkLen = cbDataLen;
    1342             :                 }
    1343           0 :                 szWrkData[nChunkLen] = '\0';
    1344             : 
    1345           0 :                 m_papszColValues[iCol] = static_cast<char *>(
    1346           0 :                     CPLRealloc(m_papszColValues[iCol],
    1347           0 :                                m_panColValueLengths[iCol] + nChunkLen + 2));
    1348           0 :                 memcpy(m_papszColValues[iCol] + m_panColValueLengths[iCol],
    1349             :                        szWrkData, nChunkLen);
    1350           0 :                 m_panColValueLengths[iCol] += nChunkLen;
    1351           0 :                 m_papszColValues[iCol][m_panColValueLengths[iCol]] = '\0';
    1352           0 :                 m_papszColValues[iCol][m_panColValueLengths[iCol] + 1] = '\0';
    1353           0 :             }
    1354             :         }
    1355             :         else
    1356             :         {
    1357           0 :             m_panColValueLengths[iCol] = cbDataLen;
    1358           0 :             m_papszColValues[iCol] =
    1359           0 :                 static_cast<char *>(CPLMalloc(cbDataLen + 2));
    1360           0 :             memcpy(m_papszColValues[iCol], szWrkData, cbDataLen);
    1361           0 :             m_papszColValues[iCol][cbDataLen] = '\0';
    1362           0 :             m_papszColValues[iCol][cbDataLen + 1] = '\0';
    1363             :         }
    1364             : 
    1365             :         // Convert WCHAR to UTF-8, assuming the WCHAR is UCS-2.
    1366           0 :         if (nFetchType == SQL_C_WCHAR && m_papszColValues[iCol] != nullptr &&
    1367           0 :             m_panColValueLengths[iCol] > 0)
    1368             :         {
    1369             : #if WCHAR_MAX == 0xFFFFu
    1370             :             wchar_t *pwszSrc =
    1371             :                 reinterpret_cast<wchar_t *>(m_papszColValues[iCol]);
    1372             : #else
    1373           0 :             unsigned int i = 0;
    1374           0 :             GUInt16 *panColValue =
    1375           0 :                 reinterpret_cast<GUInt16 *>(m_papszColValues[iCol]);
    1376           0 :             wchar_t *pwszSrc = static_cast<wchar_t *>(CPLMalloc(
    1377           0 :                 (m_panColValueLengths[iCol] / 2 + 1) * sizeof(wchar_t)));
    1378             : 
    1379           0 :             while (panColValue[i] != 0)
    1380             :             {
    1381           0 :                 pwszSrc[i] = static_cast<wchar_t>(panColValue[i]);
    1382           0 :                 i += 1;
    1383             :             }
    1384           0 :             pwszSrc[i] = L'\0';
    1385             : 
    1386           0 :             CPLFree(panColValue);
    1387             : #endif
    1388             : 
    1389           0 :             m_papszColValues[iCol] =
    1390           0 :                 CPLRecodeFromWChar(pwszSrc, CPL_ENC_UCS2, CPL_ENC_UTF8);
    1391           0 :             m_panColValueLengths[iCol] = strlen(m_papszColValues[iCol]);
    1392             : 
    1393           0 :             CPLFree(pwszSrc);
    1394             :         }
    1395             :     }
    1396             : 
    1397           0 :     return TRUE;
    1398             : }
    1399             : 
    1400             : /************************************************************************/
    1401             : /*                             GetColData()                             */
    1402             : /************************************************************************/
    1403             : 
    1404             : /**
    1405             :  * Fetch column data.
    1406             :  *
    1407             :  * Fetches the data contents of the requested column for the currently loaded
    1408             :  * row.  The result is returned as a string regardless of the column type.
    1409             :  * NULL is returned if an illegal column is given, or if the actual column
    1410             :  * is "NULL".
    1411             :  *
    1412             :  * @param iCol the zero based column to fetch.
    1413             :  *
    1414             :  * @param pszDefault the value to return if the column does not exist, or is
    1415             :  * NULL.  Defaults to NULL.
    1416             :  *
    1417             :  * @return pointer to internal column data or NULL on failure.
    1418             :  */
    1419             : 
    1420           0 : const char *CPLODBCStatement::GetColData(int iCol, const char *pszDefault)
    1421             : 
    1422             : {
    1423           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1424           0 :         return pszDefault;
    1425           0 :     else if (m_papszColValues[iCol] != nullptr)
    1426           0 :         return m_papszColValues[iCol];
    1427             :     else
    1428           0 :         return pszDefault;
    1429             : }
    1430             : 
    1431             : /************************************************************************/
    1432             : /*                             GetColData()                             */
    1433             : /************************************************************************/
    1434             : 
    1435             : /**
    1436             :  * Fetch column data.
    1437             :  *
    1438             :  * Fetches the data contents of the requested column for the currently loaded
    1439             :  * row.  The result is returned as a string regardless of the column type.
    1440             :  * NULL is returned if an illegal column is given, or if the actual column
    1441             :  * is "NULL".
    1442             :  *
    1443             :  * @param pszColName the name of the column requested.
    1444             :  *
    1445             :  * @param pszDefault the value to return if the column does not exist, or is
    1446             :  * NULL.  Defaults to NULL.
    1447             :  *
    1448             :  * @return pointer to internal column data or NULL on failure.
    1449             :  */
    1450             : 
    1451           0 : const char *CPLODBCStatement::GetColData(const char *pszColName,
    1452             :                                          const char *pszDefault)
    1453             : 
    1454             : {
    1455           0 :     const int iCol = GetColId(pszColName);
    1456             : 
    1457           0 :     if (iCol == -1)
    1458           0 :         return pszDefault;
    1459             :     else
    1460           0 :         return GetColData(iCol, pszDefault);
    1461             : }
    1462             : 
    1463             : /************************************************************************/
    1464             : /*                          GetColDataLength()                          */
    1465             : /************************************************************************/
    1466             : 
    1467             : /** GetColDataLength */
    1468           0 : int CPLODBCStatement::GetColDataLength(int iCol)
    1469             : 
    1470             : {
    1471           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1472           0 :         return 0;
    1473           0 :     else if (m_papszColValues[iCol] != nullptr)
    1474           0 :         return static_cast<int>(m_panColValueLengths[iCol]);
    1475             :     else
    1476           0 :         return 0;
    1477             : }
    1478             : 
    1479             : /************************************************************************/
    1480             : /*                        GetColDataAsDouble()                          */
    1481             : /************************************************************************/
    1482             : 
    1483             : /**
    1484             :  * Fetch column data as a double value.
    1485             :  *
    1486             :  * Fetches the data contents of the requested column for the currently loaded
    1487             :  * row as a double value.
    1488             :  *
    1489             :  * Returns NaN if a non-numeric column is requested or the actual column value
    1490             :  * is "NULL".
    1491             :  *
    1492             :  * @warning this method can only be used if the
    1493             :  * Flag::RetrieveNumericColumnsAsDouble flag was set for the CPLODBCStatement.
    1494             :  *
    1495             :  * @param iCol the zero based column to fetch.
    1496             :  *
    1497             :  * @return numeric column value or NaN on failure.
    1498             :  */
    1499             : 
    1500           0 : double CPLODBCStatement::GetColDataAsDouble(int iCol) const
    1501             : 
    1502             : {
    1503           0 :     if (!m_padColValuesAsDouble || iCol < 0 || iCol >= m_nColCount)
    1504           0 :         return std::numeric_limits<double>::quiet_NaN();
    1505             :     else
    1506           0 :         return m_padColValuesAsDouble[iCol];
    1507             : }
    1508             : 
    1509             : /************************************************************************/
    1510             : /*                         GetColDataAsDouble()                         */
    1511             : /************************************************************************/
    1512             : 
    1513             : /**
    1514             :  * Fetch column data as a double value.
    1515             :  *
    1516             :  * Fetches the data contents of the requested column for the currently loaded
    1517             :  * row as a double value.
    1518             :  *
    1519             :  * Returns NaN if a non-numeric column is requested or the actual column value
    1520             :  * is "NULL".
    1521             :  *
    1522             :  * @warning this method can only be used if the
    1523             :  * Flag::RetrieveNumericColumnsAsDouble flag was set for the CPLODBCStatement.
    1524             :  *
    1525             :  * @param pszColName the name of the column requested.
    1526             :  *
    1527             :  * @return numeric column value or NaN on failure.
    1528             :  */
    1529             : 
    1530           0 : double CPLODBCStatement::GetColDataAsDouble(const char *pszColName) const
    1531             : 
    1532             : {
    1533           0 :     if (!m_padColValuesAsDouble)
    1534           0 :         return std::numeric_limits<double>::quiet_NaN();
    1535             : 
    1536           0 :     const int iCol = GetColId(pszColName);
    1537             : 
    1538           0 :     if (iCol == -1)
    1539           0 :         return std::numeric_limits<double>::quiet_NaN();
    1540             :     else
    1541           0 :         return GetColDataAsDouble(iCol);
    1542             : }
    1543             : 
    1544             : /************************************************************************/
    1545             : /*                              GetColId()                              */
    1546             : /************************************************************************/
    1547             : 
    1548             : /**
    1549             :  * Fetch column index.
    1550             :  *
    1551             :  * Gets the column index corresponding with the passed name.  The
    1552             :  * name comparisons are case insensitive.
    1553             :  *
    1554             :  * @param pszColName the name to search for.
    1555             :  *
    1556             :  * @return the column index, or -1 if not found.
    1557             :  */
    1558             : 
    1559           0 : int CPLODBCStatement::GetColId(const char *pszColName) const
    1560             : 
    1561             : {
    1562           0 :     for (SQLSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1563           0 :         if (EQUAL(pszColName, m_papszColNames[iCol]))
    1564           0 :             return iCol;
    1565             : 
    1566           0 :     return -1;
    1567             : }
    1568             : 
    1569             : /************************************************************************/
    1570             : /*                          ClearColumnData()                           */
    1571             : /************************************************************************/
    1572             : 
    1573             : /** ClearColumnData */
    1574           0 : void CPLODBCStatement::ClearColumnData()
    1575             : 
    1576             : {
    1577           0 :     if (m_nColCount > 0)
    1578             :     {
    1579           0 :         for (int iCol = 0; iCol < m_nColCount; iCol++)
    1580             :         {
    1581           0 :             if (m_papszColValues[iCol] != nullptr)
    1582             :             {
    1583           0 :                 CPLFree(m_papszColValues[iCol]);
    1584           0 :                 m_papszColValues[iCol] = nullptr;
    1585             :             }
    1586             :         }
    1587             :     }
    1588           0 : }
    1589             : 
    1590             : /************************************************************************/
    1591             : /*                               Failed()                               */
    1592             : /************************************************************************/
    1593             : 
    1594             : //! @cond Doxygen_Suppress
    1595             : /** Failed */
    1596           0 : int CPLODBCStatement::Failed(int nResultCode)
    1597             : 
    1598             : {
    1599           0 :     if (m_poSession != nullptr)
    1600           0 :         return m_poSession->Failed(nResultCode, m_hStmt);
    1601             : 
    1602           0 :     return TRUE;
    1603             : }
    1604             : 
    1605             : //! @endcond
    1606             : 
    1607             : /************************************************************************/
    1608             : /*                         Append(const char *)                         */
    1609             : /************************************************************************/
    1610             : 
    1611             : /**
    1612             :  * Append text to internal command.
    1613             :  *
    1614             :  * The passed text is appended to the internal SQL command text.
    1615             :  *
    1616             :  * @param pszText text to append.
    1617             :  */
    1618             : 
    1619           0 : void CPLODBCStatement::Append(const char *pszText)
    1620             : 
    1621             : {
    1622           0 :     const size_t nTextLen = strlen(pszText);
    1623             : 
    1624           0 :     if (m_nStatementMax < m_nStatementLen + nTextLen + 1)
    1625             :     {
    1626           0 :         m_nStatementMax = (m_nStatementLen + nTextLen) * 2 + 100;
    1627           0 :         if (m_pszStatement == nullptr)
    1628             :         {
    1629           0 :             m_pszStatement = static_cast<char *>(VSIMalloc(m_nStatementMax));
    1630           0 :             m_pszStatement[0] = '\0';
    1631             :         }
    1632             :         else
    1633             :         {
    1634           0 :             m_pszStatement = static_cast<char *>(
    1635           0 :                 CPLRealloc(m_pszStatement, m_nStatementMax));
    1636             :         }
    1637             :     }
    1638             : 
    1639           0 :     strcpy(m_pszStatement + m_nStatementLen, pszText);
    1640           0 :     m_nStatementLen += nTextLen;
    1641           0 : }
    1642             : 
    1643             : /************************************************************************/
    1644             : /*                      Append(const std::string &)                     */
    1645             : /************************************************************************/
    1646             : 
    1647             : /**
    1648             :  * Append text to internal command.
    1649             :  *
    1650             :  * The passed text is appended to the internal SQL command text.
    1651             :  *
    1652             :  * @param s text to append.
    1653             :  */
    1654             : 
    1655           0 : void CPLODBCStatement::Append(const std::string &s)
    1656             : 
    1657             : {
    1658           0 :     Append(s.c_str());
    1659           0 : }
    1660             : 
    1661             : /************************************************************************/
    1662             : /*                     AppendEscaped(const char *)                      */
    1663             : /************************************************************************/
    1664             : 
    1665             : /**
    1666             :  * Append text to internal command.
    1667             :  *
    1668             :  * The passed text is appended to the internal SQL command text after
    1669             :  * escaping any special characters so it can be used as a character string
    1670             :  * in an SQL statement.
    1671             :  *
    1672             :  * @param pszText text to append.
    1673             :  */
    1674             : 
    1675           0 : void CPLODBCStatement::AppendEscaped(const char *pszText)
    1676             : 
    1677             : {
    1678           0 :     const size_t nTextLen = strlen(pszText);
    1679           0 :     char *pszEscapedText = static_cast<char *>(VSIMalloc(nTextLen * 2 + 1));
    1680             : 
    1681           0 :     size_t iOut = 0;  // Used after for.
    1682           0 :     for (size_t iIn = 0; iIn < nTextLen; iIn++)
    1683             :     {
    1684           0 :         switch (pszText[iIn])
    1685             :         {
    1686           0 :             case '\'':
    1687             :             case '\\':
    1688           0 :                 pszEscapedText[iOut++] = '\\';
    1689           0 :                 pszEscapedText[iOut++] = pszText[iIn];
    1690           0 :                 break;
    1691             : 
    1692           0 :             default:
    1693           0 :                 pszEscapedText[iOut++] = pszText[iIn];
    1694           0 :                 break;
    1695             :         }
    1696             :     }
    1697             : 
    1698           0 :     pszEscapedText[iOut] = '\0';
    1699             : 
    1700           0 :     Append(pszEscapedText);
    1701           0 :     CPLFree(pszEscapedText);
    1702           0 : }
    1703             : 
    1704             : /************************************************************************/
    1705             : /*                             Append(int)                              */
    1706             : /************************************************************************/
    1707             : 
    1708             : /**
    1709             :  * Append to internal command.
    1710             :  *
    1711             :  * The passed value is formatted and appended to the internal SQL command text.
    1712             :  *
    1713             :  * @param nValue value to append to the command.
    1714             :  */
    1715             : 
    1716           0 : void CPLODBCStatement::Append(int nValue)
    1717             : 
    1718             : {
    1719           0 :     char szFormattedValue[32] = {};
    1720             : 
    1721           0 :     snprintf(szFormattedValue, sizeof(szFormattedValue), "%d", nValue);
    1722           0 :     Append(szFormattedValue);
    1723           0 : }
    1724             : 
    1725             : /************************************************************************/
    1726             : /*                            Append(double)                            */
    1727             : /************************************************************************/
    1728             : 
    1729             : /**
    1730             :  * Append to internal command.
    1731             :  *
    1732             :  * The passed value is formatted and appended to the internal SQL command text.
    1733             :  *
    1734             :  * @param dfValue value to append to the command.
    1735             :  */
    1736             : 
    1737           0 : void CPLODBCStatement::Append(double dfValue)
    1738             : 
    1739             : {
    1740           0 :     char szFormattedValue[100] = {};
    1741             : 
    1742           0 :     snprintf(szFormattedValue, sizeof(szFormattedValue), "%24g", dfValue);
    1743           0 :     Append(szFormattedValue);
    1744           0 : }
    1745             : 
    1746             : /************************************************************************/
    1747             : /*                              Appendf()                               */
    1748             : /************************************************************************/
    1749             : 
    1750             : /**
    1751             :  * Append to internal command.
    1752             :  *
    1753             :  * The passed format is used to format other arguments and the result is
    1754             :  * appended to the internal command text.  Long results may not be formatted
    1755             :  * properly, and should be appended with the direct Append() methods.
    1756             :  *
    1757             :  * @param pszFormat printf() style format string.
    1758             :  *
    1759             :  * @return FALSE if formatting fails due to result being too large.
    1760             :  */
    1761             : 
    1762           0 : int CPLODBCStatement::Appendf(CPL_FORMAT_STRING(const char *pszFormat), ...)
    1763             : 
    1764             : {
    1765             :     va_list args;
    1766             : 
    1767           0 :     va_start(args, pszFormat);
    1768             : 
    1769           0 :     char szFormattedText[8000] = {};  // TODO: Move this off the stack.
    1770           0 :     szFormattedText[0] = '\0';
    1771             : 
    1772             : #if defined(HAVE_VSNPRINTF)
    1773             :     const bool bSuccess =
    1774           0 :         vsnprintf(szFormattedText, sizeof(szFormattedText) - 1, pszFormat,
    1775           0 :                   args) < static_cast<int>(sizeof(szFormattedText) - 1);
    1776             : #else
    1777             :     vsprintf(szFormattedText, pszFormat, args);
    1778             :     const bool bSuccess = true;
    1779             : #endif
    1780           0 :     va_end(args);
    1781             : 
    1782           0 :     if (bSuccess)
    1783           0 :         Append(szFormattedText);
    1784             : 
    1785           0 :     return bSuccess;
    1786             : }
    1787             : 
    1788             : /************************************************************************/
    1789             : /*                               Clear()                                */
    1790             : /************************************************************************/
    1791             : 
    1792             : /**
    1793             :  * Clear internal command text and result set definitions.
    1794             :  */
    1795             : 
    1796           0 : void CPLODBCStatement::Clear()
    1797             : 
    1798             : {
    1799             :     /* Closing the cursor if opened */
    1800           0 :     if (m_hStmt != nullptr)
    1801           0 :         SQLFreeStmt(m_hStmt, SQL_CLOSE);
    1802             : 
    1803           0 :     ClearColumnData();
    1804             : 
    1805           0 :     if (m_pszStatement != nullptr)
    1806             :     {
    1807           0 :         CPLFree(m_pszStatement);
    1808           0 :         m_pszStatement = nullptr;
    1809             :     }
    1810             : 
    1811           0 :     m_nStatementLen = 0;
    1812           0 :     m_nStatementMax = 0;
    1813             : 
    1814           0 :     m_nColCount = 0;
    1815             : 
    1816           0 :     if (m_papszColNames)
    1817             :     {
    1818           0 :         CPLFree(m_panColType);
    1819           0 :         m_panColType = nullptr;
    1820             : 
    1821           0 :         CSLDestroy(m_papszColTypeNames);
    1822           0 :         m_papszColTypeNames = nullptr;
    1823             : 
    1824           0 :         CPLFree(m_panColSize);
    1825           0 :         m_panColSize = nullptr;
    1826             : 
    1827           0 :         CPLFree(m_panColPrecision);
    1828           0 :         m_panColPrecision = nullptr;
    1829             : 
    1830           0 :         CPLFree(m_panColNullable);
    1831           0 :         m_panColNullable = nullptr;
    1832             : 
    1833           0 :         CSLDestroy(m_papszColColumnDef);
    1834           0 :         m_papszColColumnDef = nullptr;
    1835             : 
    1836           0 :         CSLDestroy(m_papszColNames);
    1837           0 :         m_papszColNames = nullptr;
    1838             : 
    1839           0 :         if (m_papszColValues)
    1840             :         {
    1841           0 :             CPLFree(m_papszColValues);
    1842           0 :             m_papszColValues = nullptr;
    1843             :         }
    1844             : 
    1845           0 :         CPLFree(m_panColValueLengths);
    1846           0 :         m_panColValueLengths = nullptr;
    1847             : 
    1848           0 :         CPLFree(m_padColValuesAsDouble);
    1849           0 :         m_padColValuesAsDouble = nullptr;
    1850             :     }
    1851           0 : }
    1852             : 
    1853             : /************************************************************************/
    1854             : /*                             GetColumns()                             */
    1855             : /************************************************************************/
    1856             : 
    1857             : /**
    1858             :  * Fetch column definitions for a table.
    1859             :  *
    1860             :  * The SQLColumn() method is used to fetch the definitions for the columns
    1861             :  * of a table (or other queryable object such as a view).  The column
    1862             :  * definitions are digested and used to populate the CPLODBCStatement
    1863             :  * column definitions essentially as if a "SELECT * FROM tablename" had
    1864             :  * been done; however, no resultset will be available.
    1865             :  *
    1866             :  * @param pszTable the name of the table to query information on.  This
    1867             :  * should not be empty.
    1868             :  *
    1869             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    1870             :  * default) if no catalog is available.
    1871             :  *
    1872             :  * @param pszSchema the schema to find the table in, use NULL (the
    1873             :  * default) if no schema is available.
    1874             :  *
    1875             :  * @return TRUE on success or FALSE on failure.
    1876             :  */
    1877             : 
    1878           0 : int CPLODBCStatement::GetColumns(const char *pszTable, const char *pszCatalog,
    1879             :                                  const char *pszSchema)
    1880             : 
    1881             : {
    1882             : #ifdef notdef
    1883             :     if (pszCatalog == nullptr)
    1884             :         pszCatalog = "";
    1885             :     if (pszSchema == nullptr)
    1886             :         pszSchema = "";
    1887             : #endif
    1888             : 
    1889             : #if (ODBCVER >= 0x0300)
    1890             : 
    1891           0 :     if (!m_poSession->IsInTransaction())
    1892             :     {
    1893             :         /* commit pending transactions and set to autocommit mode*/
    1894           0 :         m_poSession->ClearTransaction();
    1895             :     }
    1896             : 
    1897             : #endif
    1898             :     /* -------------------------------------------------------------------- */
    1899             :     /*      Fetch columns resultset for this table.                         */
    1900             :     /* -------------------------------------------------------------------- */
    1901           0 :     if (Failed(SQLColumns(
    1902             :             m_hStmt,
    1903             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    1904             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    1905             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszTable)),
    1906           0 :             SQL_NTS, nullptr /* "" */, SQL_NTS)))
    1907           0 :         return FALSE;
    1908             : 
    1909             : /* -------------------------------------------------------------------- */
    1910             : /*      Allocate per column information.                                */
    1911             : /* -------------------------------------------------------------------- */
    1912             : #ifdef notdef
    1913             :     // SQLRowCount() is too unreliable (with unixodbc on AIX for instance)
    1914             :     // so we now avoid it.
    1915             :     SQLINTEGER nResultCount = 0;
    1916             : 
    1917             :     if (Failed(SQLRowCount(m_hStmt, &nResultCount)))
    1918             :         nResultCount = 0;
    1919             : 
    1920             :     if (nResultCount < 1)
    1921             :         m_nColCount = 500;  // Hopefully lots.
    1922             :     else
    1923             :         m_nColCount = nResultCount;
    1924             : #endif
    1925             : 
    1926           0 :     m_nColCount = 500;
    1927             : 
    1928           0 :     m_papszColNames =
    1929           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1930           0 :     m_papszColValues =
    1931           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1932             : 
    1933           0 :     m_panColType =
    1934           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1935           0 :     m_papszColTypeNames =
    1936           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1937           0 :     m_panColSize =
    1938           0 :         static_cast<CPL_SQLULEN *>(CPLCalloc(sizeof(CPL_SQLULEN), m_nColCount));
    1939           0 :     m_panColPrecision =
    1940           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1941           0 :     m_panColNullable =
    1942           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1943           0 :     m_papszColColumnDef =
    1944           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1945             : 
    1946             :     /* -------------------------------------------------------------------- */
    1947             :     /*      Establish columns to use for key information.                   */
    1948             :     /* -------------------------------------------------------------------- */
    1949           0 :     for (SQLUSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1950             :     {
    1951           0 :         if (Failed(SQLFetch(m_hStmt)))
    1952             :         {
    1953           0 :             m_nColCount = iCol;
    1954           0 :             break;
    1955             :         }
    1956             : 
    1957           0 :         char szWrkData[8193] = {};
    1958           0 :         CPL_SQLLEN cbDataLen = 0;
    1959             : 
    1960           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_NAME, SQL_C_CHAR, szWrkData,
    1961             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1962           0 :         m_papszColNames[iCol] = CPLStrdup(szWrkData);
    1963             : 
    1964           0 :         SQLGetData(m_hStmt, SQLColumns_DATA_TYPE, SQL_C_CHAR, szWrkData,
    1965             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1966           0 :         m_panColType[iCol] = static_cast<short>(atoi(szWrkData));
    1967             : 
    1968           0 :         SQLGetData(m_hStmt, SQLColumns_TYPE_NAME, SQL_C_CHAR, szWrkData,
    1969             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1970           0 :         m_papszColTypeNames[iCol] = CPLStrdup(szWrkData);
    1971             : 
    1972           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_SIZE, SQL_C_CHAR, szWrkData,
    1973             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1974           0 :         m_panColSize[iCol] = atoi(szWrkData);
    1975             : 
    1976           0 :         SQLGetData(m_hStmt, SQLColumns_DECIMAL_DIGITS, SQL_C_CHAR, szWrkData,
    1977             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1978           0 :         m_panColPrecision[iCol] = static_cast<short>(atoi(szWrkData));
    1979             : 
    1980           0 :         SQLGetData(m_hStmt, SQLColumns_NULLABLE, SQL_C_CHAR, szWrkData,
    1981             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1982           0 :         m_panColNullable[iCol] = atoi(szWrkData) == SQL_NULLABLE;
    1983             : #if (ODBCVER >= 0x0300)
    1984           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_DEF, SQL_C_CHAR, szWrkData,
    1985             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1986           0 :         if (cbDataLen > 0)
    1987           0 :             m_papszColColumnDef[iCol] = CPLStrdup(szWrkData);
    1988             : #endif
    1989             :     }
    1990             : 
    1991           0 :     return TRUE;
    1992             : }
    1993             : 
    1994             : /************************************************************************/
    1995             : /*                           GetPrimaryKeys()                           */
    1996             : /************************************************************************/
    1997             : 
    1998             : /**
    1999             :  * Fetch primary keys for a table.
    2000             :  *
    2001             :  * The SQLPrimaryKeys() function is used to fetch a list of fields
    2002             :  * forming the primary key.  The result is returned as a result set matching
    2003             :  * the SQLPrimaryKeys() function result set.  The 4th column in the result
    2004             :  * set is the column name of the key, and if the result set contains only
    2005             :  * one record then that single field will be the complete primary key.
    2006             :  *
    2007             :  * @param pszTable the name of the table to query information on.  This
    2008             :  * should not be empty.
    2009             :  *
    2010             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    2011             :  * default) if no catalog is available.
    2012             :  *
    2013             :  * @param pszSchema the schema to find the table in, use NULL (the
    2014             :  * default) if no schema is available.
    2015             :  *
    2016             :  * @return TRUE on success or FALSE on failure.
    2017             :  */
    2018             : 
    2019           0 : int CPLODBCStatement::GetPrimaryKeys(const char *pszTable,
    2020             :                                      const char *pszCatalog,
    2021             :                                      const char *pszSchema)
    2022             : 
    2023             : {
    2024           0 :     if (pszCatalog == nullptr)
    2025           0 :         pszCatalog = "";
    2026           0 :     if (pszSchema == nullptr)
    2027           0 :         pszSchema = "";
    2028             : 
    2029             : #if (ODBCVER >= 0x0300)
    2030             : 
    2031           0 :     if (!m_poSession->IsInTransaction())
    2032             :     {
    2033             :         /* commit pending transactions and set to autocommit mode*/
    2034           0 :         m_poSession->ClearTransaction();
    2035             :     }
    2036             : 
    2037             : #endif
    2038             : 
    2039             :     /* -------------------------------------------------------------------- */
    2040             :     /*      Fetch columns resultset for this table.                         */
    2041             :     /* -------------------------------------------------------------------- */
    2042           0 :     if (Failed(SQLPrimaryKeys(
    2043             :             m_hStmt,
    2044             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    2045             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    2046             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszTable)),
    2047           0 :             SQL_NTS)))
    2048           0 :         return FALSE;
    2049             : 
    2050           0 :     return CollectResultsInfo();
    2051             : }
    2052             : 
    2053             : /************************************************************************/
    2054             : /*                             GetTables()                              */
    2055             : /************************************************************************/
    2056             : 
    2057             : /**
    2058             :  * Fetch tables in database.
    2059             :  *
    2060             :  * The SQLTables() function is used to fetch a list tables in the
    2061             :  * database.    The result is returned as a result set matching
    2062             :  * the SQLTables() function result set.  The 3rd column in the result
    2063             :  * set is the table name.  Only tables of type "TABLE" are returned.
    2064             :  *
    2065             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    2066             :  * default) if no catalog is available.
    2067             :  *
    2068             :  * @param pszSchema the schema to find the table in, use NULL (the
    2069             :  * default) if no schema is available.
    2070             :  *
    2071             :  * @return TRUE on success or FALSE on failure.
    2072             :  */
    2073             : 
    2074           0 : int CPLODBCStatement::GetTables(const char *pszCatalog, const char *pszSchema)
    2075             : 
    2076             : {
    2077           0 :     CPLDebug("ODBC", "CatalogNameL: %s\nSchema name: %s",
    2078             :              pszCatalog ? pszCatalog : "(null)",
    2079             :              pszSchema ? pszSchema : "(null)");
    2080             : 
    2081             : #if (ODBCVER >= 0x0300)
    2082             : 
    2083           0 :     if (!m_poSession->IsInTransaction())
    2084             :     {
    2085             :         // Commit pending transactions and set to autocommit mode.
    2086           0 :         m_poSession->ClearTransaction();
    2087             :     }
    2088             : 
    2089             : #endif
    2090             : 
    2091             :     /* -------------------------------------------------------------------- */
    2092             :     /*      Fetch columns resultset for this table.                         */
    2093             :     /* -------------------------------------------------------------------- */
    2094           0 :     if (Failed(SQLTables(
    2095             :             m_hStmt,
    2096             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    2097             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    2098             :             SQL_NTS, nullptr, SQL_NTS,
    2099             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>("'TABLE','VIEW'")),
    2100           0 :             SQL_NTS)))
    2101           0 :         return FALSE;
    2102             : 
    2103           0 :     return CollectResultsInfo();
    2104             : }
    2105             : 
    2106             : /************************************************************************/
    2107             : /*                             DumpResult()                             */
    2108             : /************************************************************************/
    2109             : 
    2110             : /**
    2111             :  * Dump resultset to file.
    2112             :  *
    2113             :  * The contents of the current resultset are dumped in a simply formatted
    2114             :  * form to the provided file.  If requested, the schema definition will
    2115             :  * be written first.
    2116             :  *
    2117             :  * @param fp the file to write to.  stdout or stderr are acceptable.
    2118             :  *
    2119             :  * @param bShowSchema TRUE to force writing schema information for the rowset
    2120             :  * before the rowset data itself.  Default is FALSE.
    2121             :  */
    2122             : 
    2123           0 : void CPLODBCStatement::DumpResult(FILE *fp, int bShowSchema)
    2124             : 
    2125             : {
    2126             :     /* -------------------------------------------------------------------- */
    2127             :     /*      Display schema                                                  */
    2128             :     /* -------------------------------------------------------------------- */
    2129           0 :     if (bShowSchema)
    2130             :     {
    2131           0 :         fprintf(fp, "Column Definitions:\n");
    2132           0 :         for (int iCol = 0; iCol < GetColCount(); iCol++)
    2133             :         {
    2134           0 :             fprintf(fp, " %2d: %-24s ", iCol, GetColName(iCol));
    2135           0 :             if (GetColPrecision(iCol) > 0 &&
    2136           0 :                 GetColPrecision(iCol) != GetColSize(iCol))
    2137           0 :                 fprintf(fp, " Size:%3d.%d", GetColSize(iCol),
    2138           0 :                         GetColPrecision(iCol));
    2139             :             else
    2140           0 :                 fprintf(fp, " Size:%5d", GetColSize(iCol));
    2141             : 
    2142           0 :             CPLString osType = GetTypeName(GetColType(iCol));
    2143           0 :             fprintf(fp, " Type:%s", osType.c_str());
    2144           0 :             if (GetColNullable(iCol))
    2145           0 :                 fprintf(fp, " NULLABLE");
    2146           0 :             fprintf(fp, "\n");
    2147             :         }
    2148           0 :         fprintf(fp, "\n");
    2149             :     }
    2150             : 
    2151             :     /* -------------------------------------------------------------------- */
    2152             :     /*      Display results                                                 */
    2153             :     /* -------------------------------------------------------------------- */
    2154           0 :     int iRecord = 0;
    2155           0 :     while (Fetch())
    2156             :     {
    2157           0 :         fprintf(fp, "Record %d\n", iRecord++);
    2158             : 
    2159           0 :         for (int iCol = 0; iCol < GetColCount(); iCol++)
    2160             :         {
    2161           0 :             fprintf(fp, "  %s: %s\n", GetColName(iCol), GetColData(iCol));
    2162             :         }
    2163             :     }
    2164           0 : }
    2165             : 
    2166             : /************************************************************************/
    2167             : /*                            GetTypeName()                             */
    2168             : /************************************************************************/
    2169             : 
    2170             : /**
    2171             :  * Get name for SQL column type.
    2172             :  *
    2173             :  * Returns a string name for the indicated type code (as returned
    2174             :  * from CPLODBCStatement::GetColType()).
    2175             :  *
    2176             :  * @param nTypeCode the SQL_ code, such as SQL_CHAR.
    2177             :  *
    2178             :  * @return internal string, "UNKNOWN" if code not recognised.
    2179             :  */
    2180             : 
    2181           0 : CPLString CPLODBCStatement::GetTypeName(int nTypeCode)
    2182             : 
    2183             : {
    2184           0 :     switch (nTypeCode)
    2185             :     {
    2186           0 :         case SQL_CHAR:
    2187           0 :             return "CHAR";
    2188             : 
    2189           0 :         case SQL_NUMERIC:
    2190           0 :             return "NUMERIC";
    2191             : 
    2192           0 :         case SQL_DECIMAL:
    2193           0 :             return "DECIMAL";
    2194             : 
    2195           0 :         case SQL_INTEGER:
    2196           0 :             return "INTEGER";
    2197             : 
    2198           0 :         case SQL_SMALLINT:
    2199           0 :             return "SMALLINT";
    2200             : 
    2201           0 :         case SQL_FLOAT:
    2202           0 :             return "FLOAT";
    2203             : 
    2204           0 :         case SQL_REAL:
    2205           0 :             return "REAL";
    2206             : 
    2207           0 :         case SQL_DOUBLE:
    2208           0 :             return "DOUBLE";
    2209             : 
    2210           0 :         case SQL_DATETIME:
    2211           0 :             return "DATETIME";
    2212             : 
    2213           0 :         case SQL_VARCHAR:
    2214           0 :             return "VARCHAR";
    2215             : 
    2216           0 :         case SQL_TYPE_DATE:
    2217           0 :             return "DATE";
    2218             : 
    2219           0 :         case SQL_TYPE_TIME:
    2220           0 :             return "TIME";
    2221             : 
    2222           0 :         case SQL_TYPE_TIMESTAMP:
    2223           0 :             return "TIMESTAMP";
    2224             : 
    2225           0 :         default:
    2226           0 :             CPLString osResult;
    2227           0 :             osResult.Printf("UNKNOWN:%d", nTypeCode);
    2228           0 :             return osResult;
    2229             :     }
    2230             : }
    2231             : 
    2232             : /************************************************************************/
    2233             : /*                            GetTypeMapping()                          */
    2234             : /************************************************************************/
    2235             : 
    2236             : /**
    2237             :  * Get appropriate C data type for SQL column type.
    2238             :  *
    2239             :  * Returns a C data type code, corresponding to the indicated SQL data
    2240             :  * type code (as returned from CPLODBCStatement::GetColType()).
    2241             :  *
    2242             :  * @param nTypeCode the SQL_ code, such as SQL_CHAR.
    2243             :  *
    2244             :  * @return data type code. The valid code is always returned. If SQL
    2245             :  * code is not recognised, SQL_C_BINARY will be returned.
    2246             :  */
    2247             : 
    2248           0 : SQLSMALLINT CPLODBCStatement::GetTypeMapping(SQLSMALLINT nTypeCode)
    2249             : 
    2250             : {
    2251           0 :     switch (nTypeCode)
    2252             :     {
    2253           0 :         case SQL_CHAR:
    2254             :         case SQL_VARCHAR:
    2255             :         case SQL_LONGVARCHAR:
    2256           0 :             return SQL_C_CHAR;
    2257             : 
    2258           0 :         case SQL_WCHAR:
    2259             :         case SQL_WVARCHAR:
    2260             :         case SQL_WLONGVARCHAR:
    2261           0 :             return SQL_C_WCHAR;
    2262             : 
    2263           0 :         case SQL_DECIMAL:
    2264             :         case SQL_NUMERIC:
    2265           0 :             return SQL_C_NUMERIC;
    2266             : 
    2267           0 :         case SQL_SMALLINT:
    2268           0 :             return SQL_C_SSHORT;
    2269             : 
    2270           0 :         case SQL_INTEGER:
    2271           0 :             return SQL_C_SLONG;
    2272             : 
    2273           0 :         case SQL_REAL:
    2274           0 :             return SQL_C_FLOAT;
    2275             : 
    2276           0 :         case SQL_FLOAT:
    2277             :         case SQL_DOUBLE:
    2278           0 :             return SQL_C_DOUBLE;
    2279             : 
    2280           0 :         case SQL_BIGINT:
    2281           0 :             return SQL_C_SBIGINT;
    2282             : 
    2283           0 :         case SQL_BIT:
    2284             :         case SQL_TINYINT:
    2285             :         // case SQL_TYPE_UTCDATETIME:
    2286             :         // case SQL_TYPE_UTCTIME:
    2287             :         case SQL_INTERVAL_MONTH:
    2288             :         case SQL_INTERVAL_YEAR:
    2289             :         case SQL_INTERVAL_YEAR_TO_MONTH:
    2290             :         case SQL_INTERVAL_DAY:
    2291             :         case SQL_INTERVAL_HOUR:
    2292             :         case SQL_INTERVAL_MINUTE:
    2293             :         case SQL_INTERVAL_SECOND:
    2294             :         case SQL_INTERVAL_DAY_TO_HOUR:
    2295             :         case SQL_INTERVAL_DAY_TO_MINUTE:
    2296             :         case SQL_INTERVAL_DAY_TO_SECOND:
    2297             :         case SQL_INTERVAL_HOUR_TO_MINUTE:
    2298             :         case SQL_INTERVAL_HOUR_TO_SECOND:
    2299             :         case SQL_INTERVAL_MINUTE_TO_SECOND:
    2300           0 :             return SQL_C_CHAR;
    2301             : 
    2302           0 :         case SQL_GUID:
    2303           0 :             return SQL_C_GUID;
    2304             : 
    2305           0 :         case SQL_DATE:
    2306             :         case SQL_TYPE_DATE:
    2307           0 :             return SQL_C_DATE;
    2308             : 
    2309           0 :         case SQL_TIME:
    2310             :         case SQL_TYPE_TIME:
    2311           0 :             return SQL_C_TIME;
    2312             : 
    2313           0 :         case SQL_TIMESTAMP:
    2314             :         case SQL_TYPE_TIMESTAMP:
    2315           0 :             return SQL_C_TIMESTAMP;
    2316             : 
    2317           0 :         case SQL_BINARY:
    2318             :         case SQL_VARBINARY:
    2319             :         case SQL_LONGVARBINARY:
    2320             :         case -151:  // SQL_SS_UDT
    2321           0 :             return SQL_C_BINARY;
    2322             : 
    2323           0 :         default:
    2324           0 :             return SQL_C_CHAR;
    2325             :     }
    2326             : }

Generated by: LCOV version 1.14