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

Generated by: LCOV version 1.14