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