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

Generated by: LCOV version 1.14