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