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

Generated by: LCOV version 1.14