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