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