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