LCOV - code coverage report
Current view: top level - port - cpl_odbc.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 101 725 13.9 %
Date: 2025-01-18 12:42:00 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             :     // coverity[negative_returns]
     848           0 :     if (Failed(SQLExecDirect(
     849           0 :             m_hStmt, reinterpret_cast<SQLCHAR *>(m_pszStatement), SQL_NTS)))
     850           0 :         return FALSE;
     851             : 
     852           0 :     return CollectResultsInfo();
     853             : }
     854             : 
     855             : /************************************************************************/
     856             : /*                         CollectResultsInfo()                         */
     857             : /************************************************************************/
     858             : 
     859             : /** CollectResultsInfo */
     860           0 : int CPLODBCStatement::CollectResultsInfo()
     861             : 
     862             : {
     863           0 :     if (m_poSession == nullptr || m_hStmt == nullptr)
     864             :     {
     865             :         // We should post an error.
     866           0 :         return FALSE;
     867             :     }
     868             : 
     869           0 :     if (Failed(SQLNumResultCols(m_hStmt, &m_nColCount)))
     870           0 :         return FALSE;
     871             : 
     872             :     /* -------------------------------------------------------------------- */
     873             :     /*      Allocate per column information.                                */
     874             :     /* -------------------------------------------------------------------- */
     875           0 :     m_papszColNames =
     876           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     877           0 :     m_papszColValues =
     878           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     879           0 :     m_panColValueLengths = static_cast<CPL_SQLLEN *>(
     880           0 :         CPLCalloc(sizeof(CPL_SQLLEN), m_nColCount + 1));
     881           0 :     if (m_nFlags & Flag::RetrieveNumericColumnsAsDouble)
     882             :     {
     883           0 :         m_padColValuesAsDouble =
     884           0 :             static_cast<double *>(CPLCalloc(sizeof(double), m_nColCount + 1));
     885             :     }
     886             :     else
     887             :     {
     888           0 :         m_padColValuesAsDouble = nullptr;
     889             :     }
     890             : 
     891           0 :     m_panColType =
     892           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     893           0 :     m_papszColTypeNames =
     894           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     895           0 :     m_panColSize =
     896           0 :         static_cast<CPL_SQLULEN *>(CPLCalloc(sizeof(CPL_SQLULEN), m_nColCount));
     897           0 :     m_panColPrecision =
     898           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     899           0 :     m_panColNullable =
     900           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
     901           0 :     m_papszColColumnDef =
     902           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
     903             : 
     904             :     /* -------------------------------------------------------------------- */
     905             :     /*      Fetch column descriptions.                                      */
     906             :     /* -------------------------------------------------------------------- */
     907           0 :     for (SQLUSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
     908             :     {
     909           0 :         SQLCHAR szName[256] = {};
     910           0 :         SQLSMALLINT nNameLength = 0;
     911             : 
     912           0 :         if (Failed(SQLDescribeCol(m_hStmt, iCol + 1, szName, sizeof(szName),
     913           0 :                                   &nNameLength, m_panColType + iCol,
     914           0 :                                   m_panColSize + iCol, m_panColPrecision + iCol,
     915           0 :                                   m_panColNullable + iCol)))
     916           0 :             return FALSE;
     917             : 
     918           0 :         szName[nNameLength] = '\0';  // Paranoid; the string should be
     919             :                                      // null-terminated by the driver.
     920           0 :         m_papszColNames[iCol] = CPLStrdup(reinterpret_cast<char *>(szName));
     921             : 
     922             :         // SQLDescribeCol() fetches just a subset of column attributes.
     923             :         // In addition to above data we need data type name.
     924           0 :         if (Failed(SQLColAttribute(m_hStmt, iCol + 1, SQL_DESC_TYPE_NAME,
     925             :                                    szName, sizeof(szName), &nNameLength,
     926           0 :                                    nullptr)))
     927           0 :             return FALSE;
     928             : 
     929           0 :         szName[nNameLength] = '\0';  // Paranoid.
     930           0 :         m_papszColTypeNames[iCol] = CPLStrdup(reinterpret_cast<char *>(szName));
     931             : 
     932             : #if DEBUG_VERBOSE
     933             :         CPLDebug("ODBC", "%s %s %d", m_papszColNames[iCol], szName,
     934             :                  m_panColType[iCol]);
     935             : #endif
     936             :     }
     937             : 
     938           0 :     return TRUE;
     939             : }
     940             : 
     941             : /************************************************************************/
     942             : /*                            GetRowCountAffected()                     */
     943             : /************************************************************************/
     944             : 
     945             : /** GetRowCountAffected */
     946           0 : int CPLODBCStatement::GetRowCountAffected()
     947             : {
     948           0 :     SQLLEN nResultCount = 0;
     949           0 :     SQLRowCount(m_hStmt, &nResultCount);
     950             : 
     951           0 :     return static_cast<int>(nResultCount);
     952             : }
     953             : 
     954             : /************************************************************************/
     955             : /*                            GetColCount()                             */
     956             : /************************************************************************/
     957             : 
     958             : /**
     959             :  * Fetch the resultset column count.
     960             :  *
     961             :  * @return the column count, or zero if there is no resultset.
     962             :  */
     963             : 
     964           0 : int CPLODBCStatement::GetColCount()
     965             : 
     966             : {
     967           0 :     return m_nColCount;
     968             : }
     969             : 
     970             : /************************************************************************/
     971             : /*                             GetColName()                             */
     972             : /************************************************************************/
     973             : 
     974             : /**
     975             :  * Fetch a column name.
     976             :  *
     977             :  * @param iCol the zero based column index.
     978             :  *
     979             :  * @return NULL on failure (out of bounds column), or a pointer to an
     980             :  * internal copy of the column name.
     981             :  */
     982             : 
     983           0 : const char *CPLODBCStatement::GetColName(int iCol)
     984             : 
     985             : {
     986           0 :     if (iCol < 0 || iCol >= m_nColCount)
     987           0 :         return nullptr;
     988             : 
     989           0 :     return m_papszColNames[iCol];
     990             : }
     991             : 
     992             : /************************************************************************/
     993             : /*                             GetColType()                             */
     994             : /************************************************************************/
     995             : 
     996             : /**
     997             :  * Fetch a column data type.
     998             :  *
     999             :  * The return type code is a an ODBC SQL_ code, one of SQL_UNKNOWN_TYPE,
    1000             :  * SQL_CHAR, SQL_NUMERIC, SQL_DECIMAL, SQL_INTEGER, SQL_SMALLINT, SQL_FLOAT,
    1001             :  * SQL_REAL, SQL_DOUBLE, SQL_DATETIME, SQL_VARCHAR, SQL_TYPE_DATE,
    1002             :  * SQL_TYPE_TIME, SQL_TYPE_TIMESTAMPT.
    1003             :  *
    1004             :  * @param iCol the zero based column index.
    1005             :  *
    1006             :  * @return type code or -1 if the column is illegal.
    1007             :  */
    1008             : 
    1009           0 : short CPLODBCStatement::GetColType(int iCol)
    1010             : 
    1011             : {
    1012           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1013           0 :         return -1;
    1014             : 
    1015           0 :     return m_panColType[iCol];
    1016             : }
    1017             : 
    1018             : /************************************************************************/
    1019             : /*                             GetColTypeName()                         */
    1020             : /************************************************************************/
    1021             : 
    1022             : /**
    1023             :  * Fetch a column data type name.
    1024             :  *
    1025             :  * Returns data source-dependent data type name; for example, "CHAR",
    1026             :  * "VARCHAR", "MONEY", "LONG VARBINAR", or "CHAR ( ) FOR BIT DATA".
    1027             :  *
    1028             :  * @param iCol the zero based column index.
    1029             :  *
    1030             :  * @return NULL on failure (out of bounds column), or a pointer to an
    1031             :  * internal copy of the column dat type name.
    1032             :  */
    1033             : 
    1034           0 : const char *CPLODBCStatement::GetColTypeName(int iCol)
    1035             : 
    1036             : {
    1037           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1038           0 :         return nullptr;
    1039             : 
    1040           0 :     return m_papszColTypeNames[iCol];
    1041             : }
    1042             : 
    1043             : /************************************************************************/
    1044             : /*                             GetColSize()                             */
    1045             : /************************************************************************/
    1046             : 
    1047             : /**
    1048             :  * Fetch the column width.
    1049             :  *
    1050             :  * @param iCol the zero based column index.
    1051             :  *
    1052             :  * @return column width, zero for unknown width columns.
    1053             :  */
    1054             : 
    1055           0 : short CPLODBCStatement::GetColSize(int iCol)
    1056             : 
    1057             : {
    1058           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1059           0 :         return -1;
    1060             : 
    1061           0 :     return static_cast<short>(m_panColSize[iCol]);
    1062             : }
    1063             : 
    1064             : /************************************************************************/
    1065             : /*                          GetColPrecision()                           */
    1066             : /************************************************************************/
    1067             : 
    1068             : /**
    1069             :  * Fetch the column precision.
    1070             :  *
    1071             :  * @param iCol the zero based column index.
    1072             :  *
    1073             :  * @return column precision, may be zero or the same as column size for
    1074             :  * columns to which it does not apply.
    1075             :  */
    1076             : 
    1077           0 : short CPLODBCStatement::GetColPrecision(int iCol)
    1078             : 
    1079             : {
    1080           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1081           0 :         return -1;
    1082             : 
    1083           0 :     return m_panColPrecision[iCol];
    1084             : }
    1085             : 
    1086             : /************************************************************************/
    1087             : /*                           GetColNullable()                           */
    1088             : /************************************************************************/
    1089             : 
    1090             : /**
    1091             :  * Fetch the column nullability.
    1092             :  *
    1093             :  * @param iCol the zero based column index.
    1094             :  *
    1095             :  * @return TRUE if the column may contains or FALSE otherwise.
    1096             :  */
    1097             : 
    1098           0 : short CPLODBCStatement::GetColNullable(int iCol)
    1099             : 
    1100             : {
    1101           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1102           0 :         return -1;
    1103             : 
    1104           0 :     return m_panColNullable[iCol];
    1105             : }
    1106             : 
    1107             : /************************************************************************/
    1108             : /*                             GetColColumnDef()                        */
    1109             : /************************************************************************/
    1110             : 
    1111             : /**
    1112             :  * Fetch a column default value.
    1113             :  *
    1114             :  * Returns the default value of a column.
    1115             :  *
    1116             :  * @param iCol the zero based column index.
    1117             :  *
    1118             :  * @return NULL if the default value is not dpecified
    1119             :  * or the internal copy of the default value.
    1120             :  */
    1121             : 
    1122           0 : const char *CPLODBCStatement::GetColColumnDef(int iCol)
    1123             : 
    1124             : {
    1125           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1126           0 :         return nullptr;
    1127             : 
    1128           0 :     return m_papszColColumnDef[iCol];
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                               Fetch()                                */
    1133             : /************************************************************************/
    1134             : 
    1135             : /**
    1136             :  * Fetch a new record.
    1137             :  *
    1138             :  * Requests the next row in the current resultset using the SQLFetchScroll()
    1139             :  * call.  Note that many ODBC drivers only support the default forward
    1140             :  * fetching one record at a time.  Only SQL_FETCH_NEXT (the default) should
    1141             :  * be considered reliable on all drivers.
    1142             :  *
    1143             :  * Currently it isn't clear how to determine whether an error or a normal
    1144             :  * out of data condition has occurred if Fetch() fails.
    1145             :  *
    1146             :  * @param nOrientation One of SQL_FETCH_NEXT, SQL_FETCH_LAST, SQL_FETCH_PRIOR,
    1147             :  * SQL_FETCH_ABSOLUTE, or SQL_FETCH_RELATIVE (default is SQL_FETCH_NEXT).
    1148             :  *
    1149             :  * @param nOffset the offset (number of records), ignored for some
    1150             :  * orientations.
    1151             :  *
    1152             :  * @return TRUE if a new row is successfully fetched, or FALSE if not.
    1153             :  */
    1154             : 
    1155           0 : int CPLODBCStatement::Fetch(int nOrientation, int nOffset)
    1156             : 
    1157             : {
    1158           0 :     ClearColumnData();
    1159             : 
    1160           0 :     if (m_hStmt == nullptr || m_nColCount < 1)
    1161           0 :         return FALSE;
    1162             : 
    1163             :     /* -------------------------------------------------------------------- */
    1164             :     /*      Fetch a new row.  Note that some brain dead drives (such as     */
    1165             :     /*      the unixodbc text file driver) don't implement                  */
    1166             :     /*      SQLScrollFetch(), so we try to stick to SQLFetch() if we        */
    1167             :     /*      can).                                                           */
    1168             :     /* -------------------------------------------------------------------- */
    1169             :     SQLRETURN nRetCode;
    1170             : 
    1171           0 :     if (nOrientation == SQL_FETCH_NEXT && nOffset == 0)
    1172             :     {
    1173           0 :         nRetCode = SQLFetch(m_hStmt);
    1174             :     }
    1175             :     else
    1176             :     {
    1177           0 :         nRetCode = SQLFetchScroll(
    1178             :             m_hStmt, static_cast<SQLSMALLINT>(nOrientation), nOffset);
    1179             :     }
    1180           0 :     if (Failed(nRetCode))
    1181             :     {
    1182           0 :         if (nRetCode != SQL_NO_DATA)
    1183             :         {
    1184           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1185           0 :                      m_poSession->GetLastError());
    1186             :         }
    1187           0 :         return FALSE;
    1188             :     }
    1189             : 
    1190             :     /* -------------------------------------------------------------------- */
    1191             :     /*      Pull out all the column values.                                 */
    1192             :     /* -------------------------------------------------------------------- */
    1193           0 :     for (SQLSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1194             :     {
    1195           0 :         CPL_SQLLEN cbDataLen = 0;
    1196           0 :         if (m_padColValuesAsDouble)
    1197           0 :             m_padColValuesAsDouble[iCol] =
    1198           0 :                 std::numeric_limits<double>::quiet_NaN();
    1199           0 :         SQLSMALLINT nFetchType = GetTypeMapping(m_panColType[iCol]);
    1200             : 
    1201             :         // Handle values other than WCHAR and BINARY as CHAR.
    1202           0 :         if (nFetchType != SQL_C_BINARY && nFetchType != SQL_C_WCHAR)
    1203           0 :             nFetchType = SQL_C_CHAR;
    1204             : 
    1205           0 :         char szWrkData[513] = {};
    1206             : 
    1207             :         // If RetrieveNumericColumnsAsDouble flag is set, then read numeric
    1208             :         // columns using numeric data types and populate native double column
    1209             :         // values array. This allows retrieval of the original numeric value as
    1210             :         // a double via GetColDataAsDouble without risk of loss of precision.
    1211             :         // Additionally, some ODBC drivers (e.g. the MS Access ODBC driver)
    1212             :         // require reading numeric values using numeric data types, otherwise
    1213             :         // incorrect values can result. See
    1214             :         // https://github.com/OSGeo/gdal/issues/3885
    1215           0 :         if (m_padColValuesAsDouble &&
    1216           0 :             (m_panColType[iCol] == SQL_DOUBLE ||
    1217           0 :              m_panColType[iCol] == SQL_FLOAT || m_panColType[iCol] == SQL_REAL))
    1218             :         {
    1219           0 :             if (m_panColType[iCol] == SQL_DOUBLE)
    1220             :             {
    1221           0 :                 double dfValue = 0;
    1222           0 :                 nRetCode = SQLGetData(m_hStmt, iCol + 1, SQL_C_DOUBLE, &dfValue,
    1223             :                                       sizeof(dfValue), &cbDataLen);
    1224           0 :                 if (cbDataLen != SQL_NULL_DATA)
    1225           0 :                     m_padColValuesAsDouble[iCol] = dfValue;
    1226             :             }
    1227             :             else
    1228             :             {
    1229             :                 // note -- cannot request a float column as SQL_C_DOUBLE when
    1230             :                 // using mdbtools driver!
    1231           0 :                 float fValue = 0;
    1232           0 :                 nRetCode = SQLGetData(m_hStmt, iCol + 1, SQL_C_FLOAT, &fValue,
    1233             :                                       sizeof(fValue), &cbDataLen);
    1234           0 :                 if (cbDataLen != SQL_NULL_DATA)
    1235           0 :                     m_padColValuesAsDouble[iCol] = static_cast<double>(fValue);
    1236             :             }
    1237           0 :             if (nRetCode != SQL_NO_DATA && Failed(nRetCode))
    1238             :             {
    1239           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1240           0 :                          m_poSession->GetLastError());
    1241           0 :                 return FALSE;
    1242             :             }
    1243             :             else
    1244             :             {
    1245           0 :                 m_papszColValues[iCol] = nullptr;
    1246           0 :                 m_panColValueLengths[iCol] = 0;
    1247           0 :                 continue;
    1248             :             }
    1249             :         }
    1250             : 
    1251           0 :         nRetCode = SQLGetData(m_hStmt, iCol + 1, nFetchType, szWrkData,
    1252             :                               sizeof(szWrkData) - 1, &cbDataLen);
    1253             : 
    1254             :         // SQLGetData() is giving garbage values in the first 4 bytes of
    1255             :         // cbDataLen in some architectures. Converting it to int discards the
    1256             :         // unnecessary bytes. This should not be a problem unless the buffer
    1257             :         // size reaches 2GB. (#3385)
    1258           0 :         cbDataLen = static_cast<int>(cbDataLen);
    1259             : 
    1260             :         // a return code of SQL_NO_DATA is not indicative of an error - see
    1261             :         // https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/getting-long-data?view=sql-server-ver15
    1262             :         // "When there is no more data to return, SQLGetData returns
    1263             :         // SQL_NO_DATA" and the example from that page which uses: while ((rc =
    1264             :         // SQLGetData(hstmt, 2, SQL_C_BINARY, BinaryPtr, sizeof(BinaryPtr),
    1265             :         // &BinaryLenOrInd)) != SQL_NO_DATA) { ... }
    1266           0 :         if (nRetCode != SQL_NO_DATA && Failed(nRetCode))
    1267             :         {
    1268           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1269           0 :                      m_poSession->GetLastError());
    1270           0 :             return FALSE;
    1271             :         }
    1272             : 
    1273             :         // if first call to SQLGetData resulted in SQL_NO_DATA return code, then
    1274             :         // the data is empty (NULL)
    1275           0 :         if (cbDataLen == SQL_NULL_DATA || nRetCode == SQL_NO_DATA)
    1276             :         {
    1277           0 :             m_papszColValues[iCol] = nullptr;
    1278           0 :             m_panColValueLengths[iCol] = 0;
    1279             :         }
    1280             : 
    1281             :         // Assume big result: should check for state=SQLSATE 01004.
    1282           0 :         else if (nRetCode == SQL_SUCCESS_WITH_INFO)
    1283             :         {
    1284           0 :             if (cbDataLen >= static_cast<CPL_SQLLEN>(sizeof(szWrkData) - 1) ||
    1285           0 :                 cbDataLen == SQL_NO_TOTAL)
    1286             :             {
    1287           0 :                 cbDataLen = static_cast<CPL_SQLLEN>(sizeof(szWrkData) - 1);
    1288           0 :                 if (nFetchType == SQL_C_CHAR)
    1289           0 :                     while ((cbDataLen > 1) && (szWrkData[cbDataLen - 1] == 0))
    1290           0 :                         --cbDataLen;  // Trimming the extra terminators: bug 990
    1291           0 :                 else if (nFetchType == SQL_C_WCHAR)
    1292           0 :                     while ((cbDataLen > 1) && (szWrkData[cbDataLen - 1] == 0) &&
    1293           0 :                            (szWrkData[cbDataLen - 2] == 0))
    1294           0 :                         cbDataLen -= 2;  // Trimming the extra terminators.
    1295             :             }
    1296             : 
    1297           0 :             m_papszColValues[iCol] =
    1298           0 :                 static_cast<char *>(CPLMalloc(cbDataLen + 2));
    1299           0 :             memcpy(m_papszColValues[iCol], szWrkData, cbDataLen);
    1300           0 :             m_papszColValues[iCol][cbDataLen] = '\0';
    1301           0 :             m_papszColValues[iCol][cbDataLen + 1] = '\0';
    1302           0 :             m_panColValueLengths[iCol] = cbDataLen;
    1303             : 
    1304             :             while (true)
    1305             :             {
    1306           0 :                 nRetCode = SQLGetData(
    1307           0 :                     m_hStmt, static_cast<SQLUSMALLINT>(iCol) + 1, nFetchType,
    1308             :                     szWrkData, sizeof(szWrkData) - 1, &cbDataLen);
    1309           0 :                 if (nRetCode == SQL_NO_DATA)
    1310           0 :                     break;
    1311             : 
    1312           0 :                 if (Failed(nRetCode))
    1313             :                 {
    1314           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1315           0 :                              m_poSession->GetLastError());
    1316           0 :                     return FALSE;
    1317             :                 }
    1318             : 
    1319             :                 CPL_SQLLEN nChunkLen;
    1320           0 :                 if (cbDataLen >= static_cast<int>(sizeof(szWrkData) - 1) ||
    1321           0 :                     cbDataLen == SQL_NO_TOTAL)
    1322             :                 {
    1323           0 :                     nChunkLen = sizeof(szWrkData) - 1;
    1324           0 :                     if (nFetchType == SQL_C_CHAR)
    1325           0 :                         while ((nChunkLen > 1) &&
    1326           0 :                                (szWrkData[nChunkLen - 1] == 0))
    1327           0 :                             --nChunkLen;  // Trimming the extra terminators.
    1328           0 :                     else if (nFetchType == SQL_C_WCHAR)
    1329           0 :                         while ((nChunkLen > 1) &&
    1330           0 :                                (szWrkData[nChunkLen - 1] == 0) &&
    1331           0 :                                (szWrkData[nChunkLen - 2] == 0))
    1332           0 :                             nChunkLen -= 2;  // Trimming the extra terminators.
    1333             :                 }
    1334             :                 else
    1335             :                 {
    1336           0 :                     nChunkLen = cbDataLen;
    1337             :                 }
    1338           0 :                 szWrkData[nChunkLen] = '\0';
    1339             : 
    1340           0 :                 m_papszColValues[iCol] = static_cast<char *>(
    1341           0 :                     CPLRealloc(m_papszColValues[iCol],
    1342           0 :                                m_panColValueLengths[iCol] + nChunkLen + 2));
    1343           0 :                 memcpy(m_papszColValues[iCol] + m_panColValueLengths[iCol],
    1344             :                        szWrkData, nChunkLen);
    1345           0 :                 m_panColValueLengths[iCol] += nChunkLen;
    1346           0 :                 m_papszColValues[iCol][m_panColValueLengths[iCol]] = '\0';
    1347           0 :                 m_papszColValues[iCol][m_panColValueLengths[iCol] + 1] = '\0';
    1348           0 :             }
    1349             :         }
    1350             :         else
    1351             :         {
    1352           0 :             m_panColValueLengths[iCol] = cbDataLen;
    1353           0 :             m_papszColValues[iCol] =
    1354           0 :                 static_cast<char *>(CPLMalloc(cbDataLen + 2));
    1355           0 :             memcpy(m_papszColValues[iCol], szWrkData, cbDataLen);
    1356           0 :             m_papszColValues[iCol][cbDataLen] = '\0';
    1357           0 :             m_papszColValues[iCol][cbDataLen + 1] = '\0';
    1358             :         }
    1359             : 
    1360             :         // Convert WCHAR to UTF-8, assuming the WCHAR is UCS-2.
    1361           0 :         if (nFetchType == SQL_C_WCHAR && m_papszColValues[iCol] != nullptr &&
    1362           0 :             m_panColValueLengths[iCol] > 0)
    1363             :         {
    1364             : #if WCHAR_MAX == 0xFFFFu
    1365             :             wchar_t *pwszSrc =
    1366             :                 reinterpret_cast<wchar_t *>(m_papszColValues[iCol]);
    1367             : #else
    1368           0 :             unsigned int i = 0;
    1369           0 :             GUInt16 *panColValue =
    1370           0 :                 reinterpret_cast<GUInt16 *>(m_papszColValues[iCol]);
    1371           0 :             wchar_t *pwszSrc = static_cast<wchar_t *>(CPLMalloc(
    1372           0 :                 (m_panColValueLengths[iCol] / 2 + 1) * sizeof(wchar_t)));
    1373             : 
    1374           0 :             while (panColValue[i] != 0)
    1375             :             {
    1376           0 :                 pwszSrc[i] = static_cast<wchar_t>(panColValue[i]);
    1377           0 :                 i += 1;
    1378             :             }
    1379           0 :             pwszSrc[i] = L'\0';
    1380             : 
    1381           0 :             CPLFree(panColValue);
    1382             : #endif
    1383             : 
    1384           0 :             m_papszColValues[iCol] =
    1385           0 :                 CPLRecodeFromWChar(pwszSrc, CPL_ENC_UCS2, CPL_ENC_UTF8);
    1386           0 :             m_panColValueLengths[iCol] = strlen(m_papszColValues[iCol]);
    1387             : 
    1388           0 :             CPLFree(pwszSrc);
    1389             :         }
    1390             :     }
    1391             : 
    1392           0 :     return TRUE;
    1393             : }
    1394             : 
    1395             : /************************************************************************/
    1396             : /*                             GetColData()                             */
    1397             : /************************************************************************/
    1398             : 
    1399             : /**
    1400             :  * Fetch column data.
    1401             :  *
    1402             :  * Fetches the data contents of the requested column for the currently loaded
    1403             :  * row.  The result is returned as a string regardless of the column type.
    1404             :  * NULL is returned if an illegal column is given, or if the actual column
    1405             :  * is "NULL".
    1406             :  *
    1407             :  * @param iCol the zero based column to fetch.
    1408             :  *
    1409             :  * @param pszDefault the value to return if the column does not exist, or is
    1410             :  * NULL.  Defaults to NULL.
    1411             :  *
    1412             :  * @return pointer to internal column data or NULL on failure.
    1413             :  */
    1414             : 
    1415           0 : const char *CPLODBCStatement::GetColData(int iCol, const char *pszDefault)
    1416             : 
    1417             : {
    1418           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1419           0 :         return pszDefault;
    1420           0 :     else if (m_papszColValues[iCol] != nullptr)
    1421           0 :         return m_papszColValues[iCol];
    1422             :     else
    1423           0 :         return pszDefault;
    1424             : }
    1425             : 
    1426             : /************************************************************************/
    1427             : /*                             GetColData()                             */
    1428             : /************************************************************************/
    1429             : 
    1430             : /**
    1431             :  * Fetch column data.
    1432             :  *
    1433             :  * Fetches the data contents of the requested column for the currently loaded
    1434             :  * row.  The result is returned as a string regardless of the column type.
    1435             :  * NULL is returned if an illegal column is given, or if the actual column
    1436             :  * is "NULL".
    1437             :  *
    1438             :  * @param pszColName the name of the column requested.
    1439             :  *
    1440             :  * @param pszDefault the value to return if the column does not exist, or is
    1441             :  * NULL.  Defaults to NULL.
    1442             :  *
    1443             :  * @return pointer to internal column data or NULL on failure.
    1444             :  */
    1445             : 
    1446           0 : const char *CPLODBCStatement::GetColData(const char *pszColName,
    1447             :                                          const char *pszDefault)
    1448             : 
    1449             : {
    1450           0 :     const int iCol = GetColId(pszColName);
    1451             : 
    1452           0 :     if (iCol == -1)
    1453           0 :         return pszDefault;
    1454             :     else
    1455           0 :         return GetColData(iCol, pszDefault);
    1456             : }
    1457             : 
    1458             : /************************************************************************/
    1459             : /*                          GetColDataLength()                          */
    1460             : /************************************************************************/
    1461             : 
    1462             : /** GetColDataLength */
    1463           0 : int CPLODBCStatement::GetColDataLength(int iCol)
    1464             : 
    1465             : {
    1466           0 :     if (iCol < 0 || iCol >= m_nColCount)
    1467           0 :         return 0;
    1468           0 :     else if (m_papszColValues[iCol] != nullptr)
    1469           0 :         return static_cast<int>(m_panColValueLengths[iCol]);
    1470             :     else
    1471           0 :         return 0;
    1472             : }
    1473             : 
    1474             : /************************************************************************/
    1475             : /*                        GetColDataAsDouble()                          */
    1476             : /************************************************************************/
    1477             : 
    1478             : /**
    1479             :  * Fetch column data as a double value.
    1480             :  *
    1481             :  * Fetches the data contents of the requested column for the currently loaded
    1482             :  * row as a double value.
    1483             :  *
    1484             :  * Returns NaN if a non-numeric column is requested or the actual column value
    1485             :  * is "NULL".
    1486             :  *
    1487             :  * @warning this method can only be used if the
    1488             :  * Flag::RetrieveNumericColumnsAsDouble flag was set for the CPLODBCStatement.
    1489             :  *
    1490             :  * @param iCol the zero based column to fetch.
    1491             :  *
    1492             :  * @return numeric column value or NaN on failure.
    1493             :  */
    1494             : 
    1495           0 : double CPLODBCStatement::GetColDataAsDouble(int iCol) const
    1496             : 
    1497             : {
    1498           0 :     if (!m_padColValuesAsDouble || iCol < 0 || iCol >= m_nColCount)
    1499           0 :         return std::numeric_limits<double>::quiet_NaN();
    1500             :     else
    1501           0 :         return m_padColValuesAsDouble[iCol];
    1502             : }
    1503             : 
    1504             : /************************************************************************/
    1505             : /*                         GetColDataAsDouble()                         */
    1506             : /************************************************************************/
    1507             : 
    1508             : /**
    1509             :  * Fetch column data as a double value.
    1510             :  *
    1511             :  * Fetches the data contents of the requested column for the currently loaded
    1512             :  * row as a double value.
    1513             :  *
    1514             :  * Returns NaN if a non-numeric column is requested or the actual column value
    1515             :  * is "NULL".
    1516             :  *
    1517             :  * @warning this method can only be used if the
    1518             :  * Flag::RetrieveNumericColumnsAsDouble flag was set for the CPLODBCStatement.
    1519             :  *
    1520             :  * @param pszColName the name of the column requested.
    1521             :  *
    1522             :  * @return numeric column value or NaN on failure.
    1523             :  */
    1524             : 
    1525           0 : double CPLODBCStatement::GetColDataAsDouble(const char *pszColName) const
    1526             : 
    1527             : {
    1528           0 :     if (!m_padColValuesAsDouble)
    1529           0 :         return std::numeric_limits<double>::quiet_NaN();
    1530             : 
    1531           0 :     const int iCol = GetColId(pszColName);
    1532             : 
    1533           0 :     if (iCol == -1)
    1534           0 :         return std::numeric_limits<double>::quiet_NaN();
    1535             :     else
    1536           0 :         return GetColDataAsDouble(iCol);
    1537             : }
    1538             : 
    1539             : /************************************************************************/
    1540             : /*                              GetColId()                              */
    1541             : /************************************************************************/
    1542             : 
    1543             : /**
    1544             :  * Fetch column index.
    1545             :  *
    1546             :  * Gets the column index corresponding with the passed name.  The
    1547             :  * name comparisons are case insensitive.
    1548             :  *
    1549             :  * @param pszColName the name to search for.
    1550             :  *
    1551             :  * @return the column index, or -1 if not found.
    1552             :  */
    1553             : 
    1554           0 : int CPLODBCStatement::GetColId(const char *pszColName) const
    1555             : 
    1556             : {
    1557           0 :     for (SQLSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1558           0 :         if (EQUAL(pszColName, m_papszColNames[iCol]))
    1559           0 :             return iCol;
    1560             : 
    1561           0 :     return -1;
    1562             : }
    1563             : 
    1564             : /************************************************************************/
    1565             : /*                          ClearColumnData()                           */
    1566             : /************************************************************************/
    1567             : 
    1568             : /** ClearColumnData */
    1569           0 : void CPLODBCStatement::ClearColumnData()
    1570             : 
    1571             : {
    1572           0 :     if (m_nColCount > 0)
    1573             :     {
    1574           0 :         for (int iCol = 0; iCol < m_nColCount; iCol++)
    1575             :         {
    1576           0 :             if (m_papszColValues[iCol] != nullptr)
    1577             :             {
    1578           0 :                 CPLFree(m_papszColValues[iCol]);
    1579           0 :                 m_papszColValues[iCol] = nullptr;
    1580             :             }
    1581             :         }
    1582             :     }
    1583           0 : }
    1584             : 
    1585             : /************************************************************************/
    1586             : /*                               Failed()                               */
    1587             : /************************************************************************/
    1588             : 
    1589             : //! @cond Doxygen_Suppress
    1590             : /** Failed */
    1591           0 : int CPLODBCStatement::Failed(int nResultCode)
    1592             : 
    1593             : {
    1594           0 :     if (m_poSession != nullptr)
    1595           0 :         return m_poSession->Failed(nResultCode, m_hStmt);
    1596             : 
    1597           0 :     return TRUE;
    1598             : }
    1599             : 
    1600             : //! @endcond
    1601             : 
    1602             : /************************************************************************/
    1603             : /*                         Append(const char *)                         */
    1604             : /************************************************************************/
    1605             : 
    1606             : /**
    1607             :  * Append text to internal command.
    1608             :  *
    1609             :  * The passed text is appended to the internal SQL command text.
    1610             :  *
    1611             :  * @param pszText text to append.
    1612             :  */
    1613             : 
    1614           0 : void CPLODBCStatement::Append(const char *pszText)
    1615             : 
    1616             : {
    1617           0 :     const size_t nTextLen = strlen(pszText);
    1618             : 
    1619           0 :     if (m_nStatementMax < m_nStatementLen + nTextLen + 1)
    1620             :     {
    1621           0 :         m_nStatementMax = (m_nStatementLen + nTextLen) * 2 + 100;
    1622           0 :         if (m_pszStatement == nullptr)
    1623             :         {
    1624           0 :             m_pszStatement = static_cast<char *>(VSIMalloc(m_nStatementMax));
    1625           0 :             m_pszStatement[0] = '\0';
    1626             :         }
    1627             :         else
    1628             :         {
    1629           0 :             m_pszStatement = static_cast<char *>(
    1630           0 :                 CPLRealloc(m_pszStatement, m_nStatementMax));
    1631             :         }
    1632             :     }
    1633             : 
    1634           0 :     strcpy(m_pszStatement + m_nStatementLen, pszText);
    1635           0 :     m_nStatementLen += nTextLen;
    1636           0 : }
    1637             : 
    1638             : /************************************************************************/
    1639             : /*                      Append(const std::string &)                     */
    1640             : /************************************************************************/
    1641             : 
    1642             : /**
    1643             :  * Append text to internal command.
    1644             :  *
    1645             :  * The passed text is appended to the internal SQL command text.
    1646             :  *
    1647             :  * @param s text to append.
    1648             :  */
    1649             : 
    1650           0 : void CPLODBCStatement::Append(const std::string &s)
    1651             : 
    1652             : {
    1653           0 :     Append(s.c_str());
    1654           0 : }
    1655             : 
    1656             : /************************************************************************/
    1657             : /*                     AppendEscaped(const char *)                      */
    1658             : /************************************************************************/
    1659             : 
    1660             : /**
    1661             :  * Append text to internal command.
    1662             :  *
    1663             :  * The passed text is appended to the internal SQL command text after
    1664             :  * escaping any special characters so it can be used as a character string
    1665             :  * in an SQL statement.
    1666             :  *
    1667             :  * @param pszText text to append.
    1668             :  */
    1669             : 
    1670           0 : void CPLODBCStatement::AppendEscaped(const char *pszText)
    1671             : 
    1672             : {
    1673           0 :     const size_t nTextLen = strlen(pszText);
    1674           0 :     char *pszEscapedText = static_cast<char *>(VSIMalloc(nTextLen * 2 + 1));
    1675             : 
    1676           0 :     size_t iOut = 0;  // Used after for.
    1677           0 :     for (size_t iIn = 0; iIn < nTextLen; iIn++)
    1678             :     {
    1679           0 :         switch (pszText[iIn])
    1680             :         {
    1681           0 :             case '\'':
    1682             :             case '\\':
    1683           0 :                 pszEscapedText[iOut++] = '\\';
    1684           0 :                 pszEscapedText[iOut++] = pszText[iIn];
    1685           0 :                 break;
    1686             : 
    1687           0 :             default:
    1688           0 :                 pszEscapedText[iOut++] = pszText[iIn];
    1689           0 :                 break;
    1690             :         }
    1691             :     }
    1692             : 
    1693           0 :     pszEscapedText[iOut] = '\0';
    1694             : 
    1695           0 :     Append(pszEscapedText);
    1696           0 :     CPLFree(pszEscapedText);
    1697           0 : }
    1698             : 
    1699             : /************************************************************************/
    1700             : /*                             Append(int)                              */
    1701             : /************************************************************************/
    1702             : 
    1703             : /**
    1704             :  * Append to internal command.
    1705             :  *
    1706             :  * The passed value is formatted and appended to the internal SQL command text.
    1707             :  *
    1708             :  * @param nValue value to append to the command.
    1709             :  */
    1710             : 
    1711           0 : void CPLODBCStatement::Append(int nValue)
    1712             : 
    1713             : {
    1714           0 :     char szFormattedValue[32] = {};
    1715             : 
    1716           0 :     snprintf(szFormattedValue, sizeof(szFormattedValue), "%d", nValue);
    1717           0 :     Append(szFormattedValue);
    1718           0 : }
    1719             : 
    1720             : /************************************************************************/
    1721             : /*                            Append(double)                            */
    1722             : /************************************************************************/
    1723             : 
    1724             : /**
    1725             :  * Append to internal command.
    1726             :  *
    1727             :  * The passed value is formatted and appended to the internal SQL command text.
    1728             :  *
    1729             :  * @param dfValue value to append to the command.
    1730             :  */
    1731             : 
    1732           0 : void CPLODBCStatement::Append(double dfValue)
    1733             : 
    1734             : {
    1735           0 :     char szFormattedValue[100] = {};
    1736             : 
    1737           0 :     snprintf(szFormattedValue, sizeof(szFormattedValue), "%24g", dfValue);
    1738           0 :     Append(szFormattedValue);
    1739           0 : }
    1740             : 
    1741             : /************************************************************************/
    1742             : /*                              Appendf()                               */
    1743             : /************************************************************************/
    1744             : 
    1745             : /**
    1746             :  * Append to internal command.
    1747             :  *
    1748             :  * The passed format is used to format other arguments and the result is
    1749             :  * appended to the internal command text.  Long results may not be formatted
    1750             :  * properly, and should be appended with the direct Append() methods.
    1751             :  *
    1752             :  * @param pszFormat printf() style format string.
    1753             :  *
    1754             :  * @return FALSE if formatting fails due to result being too large.
    1755             :  */
    1756             : 
    1757           0 : int CPLODBCStatement::Appendf(CPL_FORMAT_STRING(const char *pszFormat), ...)
    1758             : 
    1759             : {
    1760             :     va_list args;
    1761             : 
    1762           0 :     va_start(args, pszFormat);
    1763             : 
    1764           0 :     char szFormattedText[8000] = {};  // TODO: Move this off the stack.
    1765           0 :     szFormattedText[0] = '\0';
    1766             : 
    1767             : #if defined(HAVE_VSNPRINTF)
    1768             :     const bool bSuccess =
    1769           0 :         vsnprintf(szFormattedText, sizeof(szFormattedText) - 1, pszFormat,
    1770           0 :                   args) < static_cast<int>(sizeof(szFormattedText) - 1);
    1771             : #else
    1772             :     vsprintf(szFormattedText, pszFormat, args);
    1773             :     const bool bSuccess = true;
    1774             : #endif
    1775           0 :     va_end(args);
    1776             : 
    1777           0 :     if (bSuccess)
    1778           0 :         Append(szFormattedText);
    1779             : 
    1780           0 :     return bSuccess;
    1781             : }
    1782             : 
    1783             : /************************************************************************/
    1784             : /*                               Clear()                                */
    1785             : /************************************************************************/
    1786             : 
    1787             : /**
    1788             :  * Clear internal command text and result set definitions.
    1789             :  */
    1790             : 
    1791           0 : void CPLODBCStatement::Clear()
    1792             : 
    1793             : {
    1794             :     /* Closing the cursor if opened */
    1795           0 :     if (m_hStmt != nullptr)
    1796           0 :         SQLFreeStmt(m_hStmt, SQL_CLOSE);
    1797             : 
    1798           0 :     ClearColumnData();
    1799             : 
    1800           0 :     if (m_pszStatement != nullptr)
    1801             :     {
    1802           0 :         CPLFree(m_pszStatement);
    1803           0 :         m_pszStatement = nullptr;
    1804             :     }
    1805             : 
    1806           0 :     m_nStatementLen = 0;
    1807           0 :     m_nStatementMax = 0;
    1808             : 
    1809           0 :     m_nColCount = 0;
    1810             : 
    1811           0 :     if (m_papszColNames)
    1812             :     {
    1813           0 :         CPLFree(m_panColType);
    1814           0 :         m_panColType = nullptr;
    1815             : 
    1816           0 :         CSLDestroy(m_papszColTypeNames);
    1817           0 :         m_papszColTypeNames = nullptr;
    1818             : 
    1819           0 :         CPLFree(m_panColSize);
    1820           0 :         m_panColSize = nullptr;
    1821             : 
    1822           0 :         CPLFree(m_panColPrecision);
    1823           0 :         m_panColPrecision = nullptr;
    1824             : 
    1825           0 :         CPLFree(m_panColNullable);
    1826           0 :         m_panColNullable = nullptr;
    1827             : 
    1828           0 :         CSLDestroy(m_papszColColumnDef);
    1829           0 :         m_papszColColumnDef = nullptr;
    1830             : 
    1831           0 :         CSLDestroy(m_papszColNames);
    1832           0 :         m_papszColNames = nullptr;
    1833             : 
    1834           0 :         if (m_papszColValues)
    1835             :         {
    1836           0 :             CPLFree(m_papszColValues);
    1837           0 :             m_papszColValues = nullptr;
    1838             :         }
    1839             : 
    1840           0 :         CPLFree(m_panColValueLengths);
    1841           0 :         m_panColValueLengths = nullptr;
    1842             : 
    1843           0 :         CPLFree(m_padColValuesAsDouble);
    1844           0 :         m_padColValuesAsDouble = nullptr;
    1845             :     }
    1846           0 : }
    1847             : 
    1848             : /************************************************************************/
    1849             : /*                             GetColumns()                             */
    1850             : /************************************************************************/
    1851             : 
    1852             : /**
    1853             :  * Fetch column definitions for a table.
    1854             :  *
    1855             :  * The SQLColumn() method is used to fetch the definitions for the columns
    1856             :  * of a table (or other queryable object such as a view).  The column
    1857             :  * definitions are digested and used to populate the CPLODBCStatement
    1858             :  * column definitions essentially as if a "SELECT * FROM tablename" had
    1859             :  * been done; however, no resultset will be available.
    1860             :  *
    1861             :  * @param pszTable the name of the table to query information on.  This
    1862             :  * should not be empty.
    1863             :  *
    1864             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    1865             :  * default) if no catalog is available.
    1866             :  *
    1867             :  * @param pszSchema the schema to find the table in, use NULL (the
    1868             :  * default) if no schema is available.
    1869             :  *
    1870             :  * @return TRUE on success or FALSE on failure.
    1871             :  */
    1872             : 
    1873           0 : int CPLODBCStatement::GetColumns(const char *pszTable, const char *pszCatalog,
    1874             :                                  const char *pszSchema)
    1875             : 
    1876             : {
    1877             : #ifdef notdef
    1878             :     if (pszCatalog == nullptr)
    1879             :         pszCatalog = "";
    1880             :     if (pszSchema == nullptr)
    1881             :         pszSchema = "";
    1882             : #endif
    1883             : 
    1884             : #if (ODBCVER >= 0x0300)
    1885             : 
    1886           0 :     if (!m_poSession->IsInTransaction())
    1887             :     {
    1888             :         /* commit pending transactions and set to autocommit mode*/
    1889           0 :         m_poSession->ClearTransaction();
    1890             :     }
    1891             : 
    1892             : #endif
    1893             :     /* -------------------------------------------------------------------- */
    1894             :     /*      Fetch columns resultset for this table.                         */
    1895             :     /* -------------------------------------------------------------------- */
    1896           0 :     if (Failed(SQLColumns(
    1897             :             m_hStmt,
    1898             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    1899             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    1900             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszTable)),
    1901           0 :             SQL_NTS, nullptr /* "" */, SQL_NTS)))
    1902           0 :         return FALSE;
    1903             : 
    1904             : /* -------------------------------------------------------------------- */
    1905             : /*      Allocate per column information.                                */
    1906             : /* -------------------------------------------------------------------- */
    1907             : #ifdef notdef
    1908             :     // SQLRowCount() is too unreliable (with unixodbc on AIX for instance)
    1909             :     // so we now avoid it.
    1910             :     SQLINTEGER nResultCount = 0;
    1911             : 
    1912             :     if (Failed(SQLRowCount(m_hStmt, &nResultCount)))
    1913             :         nResultCount = 0;
    1914             : 
    1915             :     if (nResultCount < 1)
    1916             :         m_nColCount = 500;  // Hopefully lots.
    1917             :     else
    1918             :         m_nColCount = nResultCount;
    1919             : #endif
    1920             : 
    1921           0 :     m_nColCount = 500;
    1922             : 
    1923           0 :     m_papszColNames =
    1924           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1925           0 :     m_papszColValues =
    1926           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1927             : 
    1928           0 :     m_panColType =
    1929           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1930           0 :     m_papszColTypeNames =
    1931           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1932           0 :     m_panColSize =
    1933           0 :         static_cast<CPL_SQLULEN *>(CPLCalloc(sizeof(CPL_SQLULEN), m_nColCount));
    1934           0 :     m_panColPrecision =
    1935           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1936           0 :     m_panColNullable =
    1937           0 :         static_cast<SQLSMALLINT *>(CPLCalloc(sizeof(SQLSMALLINT), m_nColCount));
    1938           0 :     m_papszColColumnDef =
    1939           0 :         static_cast<char **>(CPLCalloc(sizeof(char *), m_nColCount + 1));
    1940             : 
    1941             :     /* -------------------------------------------------------------------- */
    1942             :     /*      Establish columns to use for key information.                   */
    1943             :     /* -------------------------------------------------------------------- */
    1944           0 :     for (SQLUSMALLINT iCol = 0; iCol < m_nColCount; iCol++)
    1945             :     {
    1946           0 :         if (Failed(SQLFetch(m_hStmt)))
    1947             :         {
    1948           0 :             m_nColCount = iCol;
    1949           0 :             break;
    1950             :         }
    1951             : 
    1952           0 :         char szWrkData[8193] = {};
    1953           0 :         CPL_SQLLEN cbDataLen = 0;
    1954             : 
    1955           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_NAME, SQL_C_CHAR, szWrkData,
    1956             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1957           0 :         m_papszColNames[iCol] = CPLStrdup(szWrkData);
    1958             : 
    1959           0 :         SQLGetData(m_hStmt, SQLColumns_DATA_TYPE, SQL_C_CHAR, szWrkData,
    1960             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1961           0 :         m_panColType[iCol] = static_cast<short>(atoi(szWrkData));
    1962             : 
    1963           0 :         SQLGetData(m_hStmt, SQLColumns_TYPE_NAME, SQL_C_CHAR, szWrkData,
    1964             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1965           0 :         m_papszColTypeNames[iCol] = CPLStrdup(szWrkData);
    1966             : 
    1967           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_SIZE, SQL_C_CHAR, szWrkData,
    1968             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1969           0 :         m_panColSize[iCol] = atoi(szWrkData);
    1970             : 
    1971           0 :         SQLGetData(m_hStmt, SQLColumns_DECIMAL_DIGITS, SQL_C_CHAR, szWrkData,
    1972             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1973           0 :         m_panColPrecision[iCol] = static_cast<short>(atoi(szWrkData));
    1974             : 
    1975           0 :         SQLGetData(m_hStmt, SQLColumns_NULLABLE, SQL_C_CHAR, szWrkData,
    1976             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1977           0 :         m_panColNullable[iCol] = atoi(szWrkData) == SQL_NULLABLE;
    1978             : #if (ODBCVER >= 0x0300)
    1979           0 :         SQLGetData(m_hStmt, SQLColumns_COLUMN_DEF, SQL_C_CHAR, szWrkData,
    1980             :                    sizeof(szWrkData) - 1, &cbDataLen);
    1981           0 :         if (cbDataLen > 0)
    1982           0 :             m_papszColColumnDef[iCol] = CPLStrdup(szWrkData);
    1983             : #endif
    1984             :     }
    1985             : 
    1986           0 :     return TRUE;
    1987             : }
    1988             : 
    1989             : /************************************************************************/
    1990             : /*                           GetPrimaryKeys()                           */
    1991             : /************************************************************************/
    1992             : 
    1993             : /**
    1994             :  * Fetch primary keys for a table.
    1995             :  *
    1996             :  * The SQLPrimaryKeys() function is used to fetch a list of fields
    1997             :  * forming the primary key.  The result is returned as a result set matching
    1998             :  * the SQLPrimaryKeys() function result set.  The 4th column in the result
    1999             :  * set is the column name of the key, and if the result set contains only
    2000             :  * one record then that single field will be the complete primary key.
    2001             :  *
    2002             :  * @param pszTable the name of the table to query information on.  This
    2003             :  * should not be empty.
    2004             :  *
    2005             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    2006             :  * default) if no catalog is available.
    2007             :  *
    2008             :  * @param pszSchema the schema to find the table in, use NULL (the
    2009             :  * default) if no schema is available.
    2010             :  *
    2011             :  * @return TRUE on success or FALSE on failure.
    2012             :  */
    2013             : 
    2014           0 : int CPLODBCStatement::GetPrimaryKeys(const char *pszTable,
    2015             :                                      const char *pszCatalog,
    2016             :                                      const char *pszSchema)
    2017             : 
    2018             : {
    2019           0 :     if (pszCatalog == nullptr)
    2020           0 :         pszCatalog = "";
    2021           0 :     if (pszSchema == nullptr)
    2022           0 :         pszSchema = "";
    2023             : 
    2024             : #if (ODBCVER >= 0x0300)
    2025             : 
    2026           0 :     if (!m_poSession->IsInTransaction())
    2027             :     {
    2028             :         /* commit pending transactions and set to autocommit mode*/
    2029           0 :         m_poSession->ClearTransaction();
    2030             :     }
    2031             : 
    2032             : #endif
    2033             : 
    2034             :     /* -------------------------------------------------------------------- */
    2035             :     /*      Fetch columns resultset for this table.                         */
    2036             :     /* -------------------------------------------------------------------- */
    2037           0 :     if (Failed(SQLPrimaryKeys(
    2038             :             m_hStmt,
    2039             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    2040             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    2041             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszTable)),
    2042           0 :             SQL_NTS)))
    2043           0 :         return FALSE;
    2044             : 
    2045           0 :     return CollectResultsInfo();
    2046             : }
    2047             : 
    2048             : /************************************************************************/
    2049             : /*                             GetTables()                              */
    2050             : /************************************************************************/
    2051             : 
    2052             : /**
    2053             :  * Fetch tables in database.
    2054             :  *
    2055             :  * The SQLTables() function is used to fetch a list tables in the
    2056             :  * database.    The result is returned as a result set matching
    2057             :  * the SQLTables() function result set.  The 3rd column in the result
    2058             :  * set is the table name.  Only tables of type "TABLE" are returned.
    2059             :  *
    2060             :  * @param pszCatalog the catalog to find the table in, use NULL (the
    2061             :  * default) if no catalog is available.
    2062             :  *
    2063             :  * @param pszSchema the schema to find the table in, use NULL (the
    2064             :  * default) if no schema is available.
    2065             :  *
    2066             :  * @return TRUE on success or FALSE on failure.
    2067             :  */
    2068             : 
    2069           0 : int CPLODBCStatement::GetTables(const char *pszCatalog, const char *pszSchema)
    2070             : 
    2071             : {
    2072           0 :     CPLDebug("ODBC", "CatalogNameL: %s\nSchema name: %s",
    2073             :              pszCatalog ? pszCatalog : "(null)",
    2074             :              pszSchema ? pszSchema : "(null)");
    2075             : 
    2076             : #if (ODBCVER >= 0x0300)
    2077             : 
    2078           0 :     if (!m_poSession->IsInTransaction())
    2079             :     {
    2080             :         // Commit pending transactions and set to autocommit mode.
    2081           0 :         m_poSession->ClearTransaction();
    2082             :     }
    2083             : 
    2084             : #endif
    2085             : 
    2086             :     /* -------------------------------------------------------------------- */
    2087             :     /*      Fetch columns resultset for this table.                         */
    2088             :     /* -------------------------------------------------------------------- */
    2089           0 :     if (Failed(SQLTables(
    2090             :             m_hStmt,
    2091             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszCatalog)),
    2092             :             SQL_NTS, reinterpret_cast<SQLCHAR *>(const_cast<char *>(pszSchema)),
    2093             :             SQL_NTS, nullptr, SQL_NTS,
    2094             :             reinterpret_cast<SQLCHAR *>(const_cast<char *>("'TABLE','VIEW'")),
    2095           0 :             SQL_NTS)))
    2096           0 :         return FALSE;
    2097             : 
    2098           0 :     return CollectResultsInfo();
    2099             : }
    2100             : 
    2101             : /************************************************************************/
    2102             : /*                             DumpResult()                             */
    2103             : /************************************************************************/
    2104             : 
    2105             : /**
    2106             :  * Dump resultset to file.
    2107             :  *
    2108             :  * The contents of the current resultset are dumped in a simply formatted
    2109             :  * form to the provided file.  If requested, the schema definition will
    2110             :  * be written first.
    2111             :  *
    2112             :  * @param fp the file to write to.  stdout or stderr are acceptable.
    2113             :  *
    2114             :  * @param bShowSchema TRUE to force writing schema information for the rowset
    2115             :  * before the rowset data itself.  Default is FALSE.
    2116             :  */
    2117             : 
    2118           0 : void CPLODBCStatement::DumpResult(FILE *fp, int bShowSchema)
    2119             : 
    2120             : {
    2121             :     /* -------------------------------------------------------------------- */
    2122             :     /*      Display schema                                                  */
    2123             :     /* -------------------------------------------------------------------- */
    2124           0 :     if (bShowSchema)
    2125             :     {
    2126           0 :         fprintf(fp, "Column Definitions:\n");
    2127           0 :         for (int iCol = 0; iCol < GetColCount(); iCol++)
    2128             :         {
    2129           0 :             fprintf(fp, " %2d: %-24s ", iCol, GetColName(iCol));
    2130           0 :             if (GetColPrecision(iCol) > 0 &&
    2131           0 :                 GetColPrecision(iCol) != GetColSize(iCol))
    2132           0 :                 fprintf(fp, " Size:%3d.%d", GetColSize(iCol),
    2133           0 :                         GetColPrecision(iCol));
    2134             :             else
    2135           0 :                 fprintf(fp, " Size:%5d", GetColSize(iCol));
    2136             : 
    2137           0 :             CPLString osType = GetTypeName(GetColType(iCol));
    2138           0 :             fprintf(fp, " Type:%s", osType.c_str());
    2139           0 :             if (GetColNullable(iCol))
    2140           0 :                 fprintf(fp, " NULLABLE");
    2141           0 :             fprintf(fp, "\n");
    2142             :         }
    2143           0 :         fprintf(fp, "\n");
    2144             :     }
    2145             : 
    2146             :     /* -------------------------------------------------------------------- */
    2147             :     /*      Display results                                                 */
    2148             :     /* -------------------------------------------------------------------- */
    2149           0 :     int iRecord = 0;
    2150           0 :     while (Fetch())
    2151             :     {
    2152           0 :         fprintf(fp, "Record %d\n", iRecord++);
    2153             : 
    2154           0 :         for (int iCol = 0; iCol < GetColCount(); iCol++)
    2155             :         {
    2156           0 :             fprintf(fp, "  %s: %s\n", GetColName(iCol), GetColData(iCol));
    2157             :         }
    2158             :     }
    2159           0 : }
    2160             : 
    2161             : /************************************************************************/
    2162             : /*                            GetTypeName()                             */
    2163             : /************************************************************************/
    2164             : 
    2165             : /**
    2166             :  * Get name for SQL column type.
    2167             :  *
    2168             :  * Returns a string name for the indicated type code (as returned
    2169             :  * from CPLODBCStatement::GetColType()).
    2170             :  *
    2171             :  * @param nTypeCode the SQL_ code, such as SQL_CHAR.
    2172             :  *
    2173             :  * @return internal string, "UNKNOWN" if code not recognised.
    2174             :  */
    2175             : 
    2176           0 : CPLString CPLODBCStatement::GetTypeName(int nTypeCode)
    2177             : 
    2178             : {
    2179           0 :     switch (nTypeCode)
    2180             :     {
    2181           0 :         case SQL_CHAR:
    2182           0 :             return "CHAR";
    2183             : 
    2184           0 :         case SQL_NUMERIC:
    2185           0 :             return "NUMERIC";
    2186             : 
    2187           0 :         case SQL_DECIMAL:
    2188           0 :             return "DECIMAL";
    2189             : 
    2190           0 :         case SQL_INTEGER:
    2191           0 :             return "INTEGER";
    2192             : 
    2193           0 :         case SQL_SMALLINT:
    2194           0 :             return "SMALLINT";
    2195             : 
    2196           0 :         case SQL_FLOAT:
    2197           0 :             return "FLOAT";
    2198             : 
    2199           0 :         case SQL_REAL:
    2200           0 :             return "REAL";
    2201             : 
    2202           0 :         case SQL_DOUBLE:
    2203           0 :             return "DOUBLE";
    2204             : 
    2205           0 :         case SQL_DATETIME:
    2206           0 :             return "DATETIME";
    2207             : 
    2208           0 :         case SQL_VARCHAR:
    2209           0 :             return "VARCHAR";
    2210             : 
    2211           0 :         case SQL_TYPE_DATE:
    2212           0 :             return "DATE";
    2213             : 
    2214           0 :         case SQL_TYPE_TIME:
    2215           0 :             return "TIME";
    2216             : 
    2217           0 :         case SQL_TYPE_TIMESTAMP:
    2218           0 :             return "TIMESTAMP";
    2219             : 
    2220           0 :         default:
    2221           0 :             CPLString osResult;
    2222           0 :             osResult.Printf("UNKNOWN:%d", nTypeCode);
    2223           0 :             return osResult;
    2224             :     }
    2225             : }
    2226             : 
    2227             : /************************************************************************/
    2228             : /*                            GetTypeMapping()                          */
    2229             : /************************************************************************/
    2230             : 
    2231             : /**
    2232             :  * Get appropriate C data type for SQL column type.
    2233             :  *
    2234             :  * Returns a C data type code, corresponding to the indicated SQL data
    2235             :  * type code (as returned from CPLODBCStatement::GetColType()).
    2236             :  *
    2237             :  * @param nTypeCode the SQL_ code, such as SQL_CHAR.
    2238             :  *
    2239             :  * @return data type code. The valid code is always returned. If SQL
    2240             :  * code is not recognised, SQL_C_BINARY will be returned.
    2241             :  */
    2242             : 
    2243           0 : SQLSMALLINT CPLODBCStatement::GetTypeMapping(SQLSMALLINT nTypeCode)
    2244             : 
    2245             : {
    2246           0 :     switch (nTypeCode)
    2247             :     {
    2248           0 :         case SQL_CHAR:
    2249             :         case SQL_VARCHAR:
    2250             :         case SQL_LONGVARCHAR:
    2251           0 :             return SQL_C_CHAR;
    2252             : 
    2253           0 :         case SQL_WCHAR:
    2254             :         case SQL_WVARCHAR:
    2255             :         case SQL_WLONGVARCHAR:
    2256           0 :             return SQL_C_WCHAR;
    2257             : 
    2258           0 :         case SQL_DECIMAL:
    2259             :         case SQL_NUMERIC:
    2260           0 :             return SQL_C_NUMERIC;
    2261             : 
    2262           0 :         case SQL_SMALLINT:
    2263           0 :             return SQL_C_SSHORT;
    2264             : 
    2265           0 :         case SQL_INTEGER:
    2266           0 :             return SQL_C_SLONG;
    2267             : 
    2268           0 :         case SQL_REAL:
    2269           0 :             return SQL_C_FLOAT;
    2270             : 
    2271           0 :         case SQL_FLOAT:
    2272             :         case SQL_DOUBLE:
    2273           0 :             return SQL_C_DOUBLE;
    2274             : 
    2275           0 :         case SQL_BIGINT:
    2276           0 :             return SQL_C_SBIGINT;
    2277             : 
    2278           0 :         case SQL_BIT:
    2279             :         case SQL_TINYINT:
    2280             :         // case SQL_TYPE_UTCDATETIME:
    2281             :         // case SQL_TYPE_UTCTIME:
    2282             :         case SQL_INTERVAL_MONTH:
    2283             :         case SQL_INTERVAL_YEAR:
    2284             :         case SQL_INTERVAL_YEAR_TO_MONTH:
    2285             :         case SQL_INTERVAL_DAY:
    2286             :         case SQL_INTERVAL_HOUR:
    2287             :         case SQL_INTERVAL_MINUTE:
    2288             :         case SQL_INTERVAL_SECOND:
    2289             :         case SQL_INTERVAL_DAY_TO_HOUR:
    2290             :         case SQL_INTERVAL_DAY_TO_MINUTE:
    2291             :         case SQL_INTERVAL_DAY_TO_SECOND:
    2292             :         case SQL_INTERVAL_HOUR_TO_MINUTE:
    2293             :         case SQL_INTERVAL_HOUR_TO_SECOND:
    2294             :         case SQL_INTERVAL_MINUTE_TO_SECOND:
    2295           0 :             return SQL_C_CHAR;
    2296             : 
    2297           0 :         case SQL_GUID:
    2298           0 :             return SQL_C_GUID;
    2299             : 
    2300           0 :         case SQL_DATE:
    2301             :         case SQL_TYPE_DATE:
    2302           0 :             return SQL_C_DATE;
    2303             : 
    2304           0 :         case SQL_TIME:
    2305             :         case SQL_TYPE_TIME:
    2306           0 :             return SQL_C_TIME;
    2307             : 
    2308           0 :         case SQL_TIMESTAMP:
    2309             :         case SQL_TYPE_TIMESTAMP:
    2310           0 :             return SQL_C_TIMESTAMP;
    2311             : 
    2312           0 :         case SQL_BINARY:
    2313             :         case SQL_VARBINARY:
    2314             :         case SQL_LONGVARBINARY:
    2315             :         case -151:  // SQL_SS_UDT
    2316           0 :             return SQL_C_BINARY;
    2317             : 
    2318           0 :         default:
    2319           0 :             return SQL_C_CHAR;
    2320             :     }
    2321             : }

Generated by: LCOV version 1.14