Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsql_odbc.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qsql_odbc_p.h"
6#include <qsqlrecord.h>
7
8#if defined (Q_OS_WIN32)
9#include <qt_windows.h>
10#endif
11#include <qcoreapplication.h>
12#include <qdatetime.h>
13#include <qlist.h>
14#include <qloggingcategory.h>
15#include <qmath.h>
16#include <qsqlerror.h>
17#include <qsqlfield.h>
18#include <qsqlindex.h>
19#include <qstringconverter.h>
20#include <qstringlist.h>
21#include <qvariant.h>
22#include <qvarlengtharray.h>
23#include <QDebug>
24#include <QSqlQuery>
25#include <QtSql/private/qsqldriver_p.h>
26#include <QtSql/private/qsqlresult_p.h>
27#include "private/qtools_p.h"
28
30
31Q_STATIC_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc")
32
33using namespace Qt::StringLiterals;
34
35// non-standard ODBC SQL data type from SQL Server sometimes used instead of SQL_TIME
36#ifndef SQL_SS_TIME2
37#define SQL_SS_TIME2 (-154)
38#endif
39
40// undefine this to prevent initial check of the ODBC driver
41#define ODBC_CHECK_DRIVER
42
43static constexpr int COLNAMESIZE = 256;
44static constexpr SQLSMALLINT TABLENAMESIZE = 128;
45//Map Qt parameter types to ODBC types
46static constexpr SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
47
49{
50public:
51 SqlStmtHandle(SQLHANDLE hDbc)
52 {
53 SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &stmtHandle);
54 }
56 {
57 if (stmtHandle != SQL_NULL_HSTMT)
58 SQLFreeHandle(SQL_HANDLE_STMT, stmtHandle);
59 }
61 {
62 return stmtHandle;
63 }
64 bool isValid() const
65 {
66 return stmtHandle != SQL_NULL_HSTMT;
67 }
69};
70
71template<typename C, int SIZE = sizeof(SQLTCHAR)>
72inline static QString fromSQLTCHAR(const C &input, qsizetype size = -1)
73{
74 // Remove any trailing \0 as some drivers misguidedly append one
75 qsizetype realsize = qMin(size, input.size());
76 if (realsize > 0 && input[realsize - 1] == 0)
77 realsize--;
78 if constexpr (SIZE == 1)
79 return QString::fromUtf8(reinterpret_cast<const char *>(input.constData()), realsize);
80 else if constexpr (SIZE == 2)
81 return QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize);
82 else if constexpr (SIZE == 4)
83 return QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize);
84 else
85 static_assert(QtPrivate::value_dependent_false<SIZE>(),
86 "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
87}
88
89template<int SIZE = sizeof(SQLTCHAR)>
91{
92 if constexpr (SIZE == 1)
93 return QStringConverter::Utf8;
94 else if constexpr (SIZE == 2)
95 return QStringConverter::Utf16;
96 else if constexpr (SIZE == 4)
97 return QStringConverter::Utf32;
98 else
99 static_assert(QtPrivate::value_dependent_false<SIZE>(),
100 "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
101}
102
103inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(QStringView input)
104{
105 QVarLengthArray<SQLTCHAR> result;
106 QStringEncoder enc(encodingForSqlTChar());
107 result.resize(enc.requiredSpace(input.size()));
108 const auto end = enc.appendToBuffer(reinterpret_cast<char *>(result.data()), input);
109 result.resize((end - reinterpret_cast<char *>(result.data())) / sizeof(SQLTCHAR));
110 return result;
111}
112
114{
115 Q_DECLARE_PUBLIC(QODBCDriver)
116
117public:
120
121 SQLHANDLE hEnv = nullptr;
122 SQLHANDLE hDbc = nullptr;
123
126 bool unicode = false;
127 bool useSchema = false;
128 bool isFreeTDSDriver = false;
129 bool hasSQLFetchScroll = true;
130 bool hasMultiResultSets = false;
131
132 bool checkDriver() const;
134 void checkDBMS();
140 bool setConnectionOptions(const QString& connOpts);
141 void splitTableQualifier(const QString &qualifier, QString &catalog,
142 QString &schema, QString &table) const;
143 QString adjustCase(const QString&) const;
146 SQLRETURN sqlFetchNext(SQLHANDLE hStmt) const;
147private:
148 bool isQuoteInitialized = false;
149 QChar quote = u'"';
150 DefaultCase m_defaultCase = DefaultCase::Mixed;
151};
152
154
156{
158
159public:
161 virtual ~QODBCResult();
162
163 bool prepare(const QString &query) override;
165
166 QVariant lastInsertId() const override;
167 QVariant handle() const override;
168
169protected:
174 bool fetch(int i) override;
175 bool reset(const QString &query) override;
176 QVariant data(int field) override;
177 bool isNull(int field) override;
180 QSqlRecord record() const override;
181 void virtual_hook(int id, void *data) override;
184};
185
187{
188 Q_DECLARE_PUBLIC(QODBCResult)
189
190public:
200
201 inline void clearValues()
203
204 SQLHANDLE dpEnv() const { return drv_d_func() ? drv_d_func()->hEnv : 0;}
205 SQLHANDLE dpDbc() const { return drv_d_func() ? drv_d_func()->hDbc : 0;}
206 SQLHANDLE hStmt = nullptr;
207
212 bool hasSQLFetchScroll = true;
213 bool unicode = false;
214 bool useSchema = false;
215
216 bool isStmtHandleValid() const;
218};
219
221{
222 return drv_d_func() && disconnectCount == drv_d_func()->disconnectCount;
223}
224
226{
227 disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0;
228}
229
236static QList<DiagRecord> qWarnODBCHandle(int handleType, SQLHANDLE handle)
237{
238 SQLINTEGER nativeCode = 0;
239 SQLSMALLINT msgLen = 0;
240 SQLSMALLINT i = 1;
241 SQLRETURN r = SQL_NO_DATA;
242 QVarLengthArray<SQLTCHAR, SQL_SQLSTATE_SIZE + 1> state(SQL_SQLSTATE_SIZE + 1);
243 QVarLengthArray<SQLTCHAR, SQL_MAX_MESSAGE_LENGTH + 1> description(SQL_MAX_MESSAGE_LENGTH + 1);
244 QList<DiagRecord> result;
245
246 if (!handle)
247 return result;
248 do {
249 r = SQLGetDiagRec(handleType,
250 handle,
251 i,
252 state.data(),
253 &nativeCode,
254 description.data(),
255 description.size(),
256 &msgLen);
257 if (msgLen >= description.size()) {
258 description.resize(msgLen + 1); // incl. \0 termination
259 continue;
260 }
261 if (SQL_SUCCEEDED(r)) {
262 result.push_back({fromSQLTCHAR(description, msgLen),
263 fromSQLTCHAR(state),
264 QString::number(nativeCode)});
265 } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
266 break;
267 }
268 ++i;
269 } while (r != SQL_NO_DATA);
270 return result;
271}
272
273static QList<DiagRecord> qODBCWarn(const SQLHANDLE hStmt,
274 const SQLHANDLE envHandle = nullptr,
275 const SQLHANDLE pDbC = nullptr)
276{
277 QList<DiagRecord> result;
278 result.append(qWarnODBCHandle(SQL_HANDLE_ENV, envHandle));
279 result.append(qWarnODBCHandle(SQL_HANDLE_DBC, pDbC));
280 result.append(qWarnODBCHandle(SQL_HANDLE_STMT, hStmt));
281 return result;
282}
283
285{
286 return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc());
287}
288
290{
291 return qODBCWarn(nullptr, odbc->hEnv, odbc->hDbc);
292}
293
295{
296 const auto add = [](const DiagRecord &a, const DiagRecord &b) {
297 return DiagRecord{a.description + u' ' + b.description,
298 a.sqlState + u';' + b.sqlState,
299 a.errorCode + u';' + b.errorCode};
300 };
301 if (records.isEmpty())
302 return {};
303 return std::accumulate(std::next(records.begin()), records.end(), records.front(), add);
304}
305
306static QSqlError errorFromDiagRecords(const QString &err,
307 QSqlError::ErrorType type,
308 const QList<DiagRecord> &records)
309{
310 if (records.empty())
311 return QSqlError("QODBC: unknown error"_L1, {}, type, {});
312 const auto combined = combineRecords(records);
313 return QSqlError("QODBC: "_L1 + err, combined.description + ", "_L1 + combined.sqlState, type,
314 combined.errorCode);
315}
316
318{
319 const auto combined = combineRecords(records);
320 return combined.description;
321}
322
323template<class T>
324static void qSqlWarning(const QString &message, T &&val)
325{
326 const auto addMsg = errorStringFromDiagRecords(qODBCWarn(val));
327 if (addMsg.isEmpty())
328 qCWarning(lcOdbc) << message;
329 else
330 qCWarning(lcOdbc) << message << "\tError:" << addMsg;
331}
332
333template <typename T>
334static QSqlError qMakeError(const QString &err,
335 QSqlError::ErrorType type,
336 const T *p)
337{
338 return errorFromDiagRecords(err, type, qODBCWarn(p));
339}
340
341static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned)
342{
343 int type = QMetaType::UnknownType;
344 switch (sqltype) {
345 case SQL_DECIMAL:
346 case SQL_NUMERIC:
347 case SQL_FLOAT: // 24 or 53 bits precision
348 case SQL_DOUBLE:// 53 bits
349 type = QMetaType::Double;
350 break;
351 case SQL_REAL: // 24 bits
352 type = QMetaType::Float;
353 break;
354 case SQL_SMALLINT:
355 type = isSigned ? QMetaType::Short : QMetaType::UShort;
356 break;
357 case SQL_INTEGER:
358 case SQL_BIT:
359 type = isSigned ? QMetaType::Int : QMetaType::UInt;
360 break;
361 case SQL_TINYINT:
362 type = QMetaType::UInt;
363 break;
364 case SQL_BIGINT:
365 type = isSigned ? QMetaType::LongLong : QMetaType::ULongLong;
366 break;
367 case SQL_BINARY:
368 case SQL_VARBINARY:
369 case SQL_LONGVARBINARY:
370 type = QMetaType::QByteArray;
371 break;
372 case SQL_DATE:
373 case SQL_TYPE_DATE:
374 type = QMetaType::QDate;
375 break;
376 case SQL_SS_TIME2:
377 case SQL_TIME:
378 case SQL_TYPE_TIME:
379 type = QMetaType::QTime;
380 break;
381 case SQL_TIMESTAMP:
382 case SQL_TYPE_TIMESTAMP:
383 type = QMetaType::QDateTime;
384 break;
385 case SQL_WCHAR:
386 case SQL_WVARCHAR:
387 case SQL_WLONGVARCHAR:
388 type = QMetaType::QString;
389 break;
390 case SQL_CHAR:
391 case SQL_VARCHAR:
392#if (ODBCVER >= 0x0350)
393 case SQL_GUID:
394#endif
395 case SQL_LONGVARCHAR:
396 type = QMetaType::QString;
397 break;
398 default:
399 type = QMetaType::QByteArray;
400 break;
401 }
402 return QMetaType(type);
403}
404
405template <typename CT>
406static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType)
407{
408 QString fieldVal;
409 SQLRETURN r = SQL_ERROR;
410 SQLLEN lengthIndicator = 0;
411 QVarLengthArray<CT> buf(colSize);
412 while (true) {
413 r = SQLGetData(hStmt,
414 column + 1,
415 targetType,
416 SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)),
417 &lengthIndicator);
418 if (SQL_SUCCEEDED(r)) {
419 if (lengthIndicator == SQL_NULL_DATA) {
420 return {};
421 }
422 // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
423 // instead of the length (which sometimes was wrong in older versions)
424 // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
425 // if length indicator equals SQL_NO_TOTAL, indicating that
426 // more data can be fetched, but size not known, collect data
427 // and fetch next block
428 if (lengthIndicator == SQL_NO_TOTAL) {
429 fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, buf.size());
430 continue;
431 }
432 // if SQL_SUCCESS_WITH_INFO is returned, indicating that
433 // more data can be fetched, the length indicator does NOT
434 // contain the number of bytes returned - it contains the
435 // total number of bytes that were transferred to our buffer
436 const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO)
437 ? buf.size()
438 : qsizetype(lengthIndicator / sizeof(CT));
439 fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, rSize);
440 // when we got SQL_SUCCESS_WITH_INFO then there is more data to fetch,
441 // otherwise we are done
442 if (r == SQL_SUCCESS)
443 break;
444 } else if (r == SQL_NO_DATA) {
445 break;
446 } else {
447 qSqlWarning("QODBC::getStringData: Error while fetching data"_L1, hStmt);
448 return {};
449 }
450 }
451 return fieldVal;
452}
453
454static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode)
455{
456 if (colSize <= 0) {
457 colSize = 256; // default Prealloc size of QVarLengthArray
458 } else if (colSize > 65536) { // limit buffer size to 64 KB
459 colSize = 65536;
460 } else {
461 colSize++; // make sure there is room for more than the 0 termination
462 }
463 return unicode ? getStringDataImpl<SQLTCHAR>(hStmt, column, colSize, SQL_C_TCHAR)
464 : getStringDataImpl<SQLCHAR>(hStmt, column, colSize, SQL_C_CHAR);
465}
466
467static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
468{
469 QByteArray fieldVal;
470 SQLSMALLINT colNameLen;
471 SQLSMALLINT colType;
472 SQLULEN colSize;
473 SQLSMALLINT colScale;
474 SQLSMALLINT nullable;
475 SQLLEN lengthIndicator = 0;
476 SQLRETURN r = SQL_ERROR;
477
478 QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
479
480 r = SQLDescribeCol(hStmt,
481 column + 1,
482 colName.data(), SQLSMALLINT(colName.size()),
483 &colNameLen,
484 &colType,
485 &colSize,
486 &colScale,
487 &nullable);
488 if (r != SQL_SUCCESS)
489 qSqlWarning(("QODBC::qGetBinaryData: Unable to describe column %1"_L1)
490 .arg(QString::number(column)), hStmt);
491 // SQLDescribeCol may return 0 if size cannot be determined
492 if (!colSize)
493 colSize = 255;
494 else if (colSize > 65536) // read the field in 64 KB chunks
495 colSize = 65536;
496 fieldVal.resize(colSize);
497 ulong read = 0;
498 while (true) {
499 r = SQLGetData(hStmt,
500 column+1,
501 SQL_C_BINARY,
502 const_cast<char *>(fieldVal.constData() + read),
503 colSize,
504 &lengthIndicator);
505 if (!SQL_SUCCEEDED(r))
506 break;
507 if (lengthIndicator == SQL_NULL_DATA)
508 return QVariant(QMetaType(QMetaType::QByteArray));
509 if (lengthIndicator > SQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) {
510 read += colSize;
511 colSize = 65536;
512 } else {
513 read += lengthIndicator;
514 }
515 if (r == SQL_SUCCESS) { // the whole field was read in one chunk
516 fieldVal.resize(read);
517 break;
518 }
519 fieldVal.resize(fieldVal.size() + colSize);
520 }
521 return fieldVal;
522}
523
524template <typename T>
525static QVariant qGetIntData(SQLHANDLE hStmt, int column)
526{
527 constexpr auto isSigned = std::is_signed<T>();
528 constexpr auto is16BitType = sizeof(T) == 2;
529 constexpr auto is32BitType = sizeof(T) == 4;
530 constexpr auto is64BitType = sizeof(T) == 8;
531 constexpr auto tgtType =
532 is16BitType ? (isSigned ? SQL_C_SSHORT : SQL_C_USHORT)
533 : is32BitType ? (isSigned ? SQL_C_SLONG : SQL_C_ULONG)
534 : (isSigned ? SQL_C_SBIGINT : SQL_C_UBIGINT);
535 static_assert(is16BitType || is32BitType || is64BitType,
536 "Can only handle 16, 32 or 64 bit integer");
537 T intbuf = 0;
538 SQLLEN lengthIndicator = 0;
539 SQLRETURN r = SQLGetData(hStmt, column + 1, tgtType,
540 (SQLPOINTER)&intbuf, sizeof(intbuf), &lengthIndicator);
541 if (!SQL_SUCCEEDED(r))
542 return QVariant();
543 if (lengthIndicator == SQL_NULL_DATA)
544 return QVariant(QMetaType::fromType<T>());
545 return QVariant::fromValue<T>(intbuf);
546}
547
548template <typename T>
549static QVariant qGetFloatingPointData(SQLHANDLE hStmt, int column)
550{
551 constexpr auto tgtType = sizeof(T) == 4 ? SQL_C_FLOAT : SQL_C_DOUBLE;
552 static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>,
553 "Can only handle float or double");
554 T buffer = 0;
555 SQLLEN lengthIndicator = 0;
556 SQLRETURN r = SQLGetData(hStmt, column + 1, tgtType,
557 (SQLPOINTER)&buffer, sizeof(buffer), &lengthIndicator);
558 if (!SQL_SUCCEEDED(r))
559 return QVariant();
560 if (lengthIndicator == SQL_NULL_DATA)
561 return QVariant(QMetaType::fromType<T>());
562 return QVariant::fromValue<T>(buffer);
563}
564
565static bool isAutoValue(const SQLHANDLE hStmt, int column)
566{
567 SQLLEN nNumericAttribute = 0; // Check for auto-increment
568 const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE,
569 0, 0, 0, &nNumericAttribute);
570 if (!SQL_SUCCEEDED(r)) {
571 qSqlWarning(("QODBC::isAutoValue: Unable to get autovalue attribute for column %1"_L1)
572 .arg(QString::number(column)), hStmt);
573 return false;
574 }
575 return nNumericAttribute != SQL_FALSE;
576}
577
578// creates a QSqlField from a valid hStmt generated
579// by SQLColumns. The hStmt has to point to a valid position.
580static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p)
581{
582 QString fname = qGetStringData(hStmt, 3, -1, p->unicode).toString();
583 int type = qGetIntData<int>(hStmt, 4).toInt(); // column type
584 QSqlField f(fname, qDecodeODBCType(type, true)); // signed, we don't know better here
585 QVariant var = qGetIntData<int>(hStmt, 6);
586 f.setLength(var.isNull() ? -1 : var.toInt()); // column size
587 var = qGetIntData<int>(hStmt, 8).toInt();
588 f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
589 int required = qGetIntData<int>(hStmt, 10).toInt(); // nullable-flag
590 // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
591 if (required == SQL_NO_NULLS)
592 f.setRequired(true);
593 else if (required == SQL_NULLABLE)
594 f.setRequired(false);
595 // else we don't know
596 return f;
597}
598
599static QSqlField qMakeFieldInfo(const QODBCResultPrivate *p, int i)
600{
601 SQLSMALLINT colNameLen;
602 SQLSMALLINT colType;
603 SQLULEN colSize;
604 SQLSMALLINT colScale;
605 SQLSMALLINT nullable;
606 SQLRETURN r = SQL_ERROR;
607 QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
608 r = SQLDescribeCol(p->hStmt,
609 i+1,
610 colName.data(), SQLSMALLINT(colName.size()),
611 &colNameLen,
612 &colType,
613 &colSize,
614 &colScale,
615 &nullable);
616
617 if (r != SQL_SUCCESS) {
618 qSqlWarning(("QODBC::qMakeFieldInfo: Unable to describe column %1"_L1)
619 .arg(QString::number(i)), p);
620 return QSqlField();
621 }
622
623 SQLLEN unsignedFlag = SQL_FALSE;
624 r = SQLColAttribute (p->hStmt,
625 i + 1,
626 SQL_DESC_UNSIGNED,
627 0,
628 0,
629 0,
630 &unsignedFlag);
631 if (r != SQL_SUCCESS) {
632 qSqlWarning(("QODBC::qMakeFieldInfo: Unable to get column attributes for column %1"_L1)
633 .arg(QString::number(i)), p);
634 }
635
636 const QString qColName(fromSQLTCHAR(colName, colNameLen));
637 // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
638 QMetaType type = qDecodeODBCType(colType, unsignedFlag == SQL_FALSE);
639 QSqlField f(qColName, type);
640 f.setLength(colSize == 0 ? -1 : int(colSize));
641 f.setPrecision(colScale == 0 ? -1 : int(colScale));
642 if (nullable == SQL_NO_NULLS)
643 f.setRequired(true);
644 else if (nullable == SQL_NULLABLE)
645 f.setRequired(false);
646 // else we don't know
647 f.setAutoValue(isAutoValue(p->hStmt, i));
648 QVarLengthArray<SQLTCHAR, TABLENAMESIZE> tableName(TABLENAMESIZE);
649 SQLSMALLINT tableNameLen;
650 r = SQLColAttribute(p->hStmt,
651 i + 1,
652 SQL_DESC_BASE_TABLE_NAME,
653 tableName.data(),
654 SQLSMALLINT(tableName.size() * sizeof(SQLTCHAR)), // SQLColAttribute needs/returns size in bytes
655 &tableNameLen,
656 0);
657 if (r == SQL_SUCCESS)
658 f.setTableName(fromSQLTCHAR(tableName, tableNameLen / sizeof(SQLTCHAR)));
659 return f;
660}
661
662static size_t qGetODBCVersion(const QString &connOpts)
663{
664 if (connOpts.contains("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"_L1, Qt::CaseInsensitive))
665 return SQL_OV_ODBC3;
666 return SQL_OV_ODBC2;
667}
668
670{
671 if (!isQuoteInitialized) {
672 SQLTCHAR driverResponse[4];
673 SQLSMALLINT length;
674 int r = SQLGetInfo(hDbc,
675 SQL_IDENTIFIER_QUOTE_CHAR,
676 &driverResponse,
677 sizeof(driverResponse),
678 &length);
679 if (SQL_SUCCEEDED(r))
680 quote = QChar(driverResponse[0]);
681 else
682 quote = u'"';
683 isQuoteInitialized = true;
684 }
685 return quote;
686}
687
689{
690 return sqlFetchNext(hStmt.handle());
691}
692
694{
696 return SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0);
697 return SQLFetch(hStmt);
698}
699
700static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, QStringView val)
701{
702 auto encoded = toSQLTCHAR(val);
703 return SQLSetConnectAttr(handle, attr,
704 encoded.data(),
705 SQLINTEGER(encoded.size() * sizeof(SQLTCHAR))); // size in bytes
706}
707
708
709bool QODBCDriverPrivate::setConnectionOptions(const QString &connOpts)
710{
711 // Set any connection attributes
712 SQLRETURN r = SQL_SUCCESS;
713 for (const auto connOpt : QStringTokenizer{connOpts, u';', Qt::SkipEmptyParts}) {
714 int idx;
715 if ((idx = connOpt.indexOf(u'=')) == -1) {
716 qSqlWarning(("QODBCDriver::open: Illegal connect option value '%1'"_L1)
717 .arg(connOpt), this);
718 continue;
719 }
720 const auto opt(connOpt.left(idx));
721 const auto val(connOpt.mid(idx + 1).trimmed());
722 SQLUINTEGER v = 0;
723
724 r = SQL_SUCCESS;
725 if (opt == "SQL_ATTR_ACCESS_MODE"_L1) {
726 if (val == "SQL_MODE_READ_ONLY"_L1) {
727 v = SQL_MODE_READ_ONLY;
728 } else if (val == "SQL_MODE_READ_WRITE"_L1) {
729 v = SQL_MODE_READ_WRITE;
730 } else {
731 qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
732 .arg(val), this);
733 continue;
734 }
735 r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0);
736 } else if (opt == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) {
737 v = val.toUInt();
738 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0);
739 } else if (opt == "SQL_ATTR_LOGIN_TIMEOUT"_L1) {
740 v = val.toUInt();
741 r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
742 } else if (opt == "SQL_ATTR_CURRENT_CATALOG"_L1) {
743 r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val);
744 } else if (opt == "SQL_ATTR_METADATA_ID"_L1) {
745 if (val == "SQL_TRUE"_L1) {
746 v = SQL_TRUE;
747 } else if (val == "SQL_FALSE"_L1) {
748 v = SQL_FALSE;
749 } else {
750 qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
751 .arg(val), this);
752 continue;
753 }
754 r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0);
755 } else if (opt == "SQL_ATTR_PACKET_SIZE"_L1) {
756 v = val.toUInt();
757 r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
758 } else if (opt == "SQL_ATTR_TRACEFILE"_L1) {
759 r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val);
760 } else if (opt == "SQL_ATTR_TRACE"_L1) {
761 if (val == "SQL_OPT_TRACE_OFF"_L1) {
762 v = SQL_OPT_TRACE_OFF;
763 } else if (val == "SQL_OPT_TRACE_ON"_L1) {
764 v = SQL_OPT_TRACE_ON;
765 } else {
766 qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
767 .arg(val), this);
768 continue;
769 }
770 r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0);
771 } else if (opt == "SQL_ATTR_CONNECTION_POOLING"_L1) {
772 if (val == "SQL_CP_OFF"_L1)
773 v = SQL_CP_OFF;
774 else if (val == "SQL_CP_ONE_PER_DRIVER"_L1)
775 v = SQL_CP_ONE_PER_DRIVER;
776 else if (val == "SQL_CP_ONE_PER_HENV"_L1)
777 v = SQL_CP_ONE_PER_HENV;
778 else if (val == "SQL_CP_DEFAULT"_L1)
779 v = SQL_CP_DEFAULT;
780 else {
781 qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
782 .arg(val), this);
783 continue;
784 }
785 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0);
786 } else if (opt == "SQL_ATTR_CP_MATCH"_L1) {
787 if (val == "SQL_CP_STRICT_MATCH"_L1)
788 v = SQL_CP_STRICT_MATCH;
789 else if (val == "SQL_CP_RELAXED_MATCH"_L1)
790 v = SQL_CP_RELAXED_MATCH;
791 else if (val == "SQL_CP_MATCH_DEFAULT"_L1)
792 v = SQL_CP_MATCH_DEFAULT;
793 else {
794 qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
795 .arg(val), this);
796 continue;
797 }
798 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0);
799 } else if (opt == "SQL_ATTR_ODBC_VERSION"_L1) {
800 // Already handled in QODBCDriver::open()
801 continue;
802 } else {
803 qSqlWarning(("QODBCDriver::open: Unknown connection attribute '%1'"_L1)
804 .arg(opt), this);
805 }
806 if (!SQL_SUCCEEDED(r))
807 qSqlWarning(("QODBCDriver::open: Unable to set connection attribute '%1'"_L1)
808 .arg(opt), this);
809 }
810 return true;
811}
812
813void QODBCDriverPrivate::splitTableQualifier(const QString &qualifier, QString &catalog,
814 QString &schema, QString &table) const
815{
816 Q_Q(const QODBCDriver);
817 const auto adjustName = [&](const QString &name) {
818 if (q->isIdentifierEscaped(name, QSqlDriver::TableName))
819 return q->stripDelimiters(name, QSqlDriver::TableName);
820 return adjustCase(name);
821 };
822 catalog.clear();
823 schema.clear();
824 table.clear();
825 if (!useSchema) {
826 table = adjustName(qualifier);
827 return;
828 }
829 const QList<QStringView> l = QStringView(qualifier).split(u'.');
830 switch (l.count()) {
831 case 1:
832 table = adjustName(qualifier);
833 break;
834 case 2:
835 schema = adjustName(l.at(0).toString());
836 table = adjustName(l.at(1).toString());
837 break;
838 case 3:
839 catalog = adjustName(l.at(0).toString());
840 schema = adjustName(l.at(1).toString());
841 table = adjustName(l.at(2).toString());
842 break;
843 default:
844 qSqlWarning(("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'"_L1)
845 .arg(qualifier), this);
846 break;
847 }
848}
849
851{
852 m_defaultCase = DefaultCase::Mixed; //arbitrary case if driver cannot be queried
853 SQLUSMALLINT casing;
854 SQLRETURN r = SQLGetInfo(hDbc,
855 SQL_IDENTIFIER_CASE,
856 &casing,
857 sizeof(casing),
858 NULL);
859 if (r == SQL_SUCCESS) {
860 switch (casing) {
861 case SQL_IC_UPPER:
862 m_defaultCase = DefaultCase::Upper;
863 break;
864 case SQL_IC_LOWER:
865 m_defaultCase = DefaultCase::Lower;
866 break;
867 case SQL_IC_SENSITIVE:
868 m_defaultCase = DefaultCase::Sensitive;
869 break;
870 case SQL_IC_MIXED:
871 m_defaultCase = DefaultCase::Mixed;
872 break;
873 }
874 }
875}
876
877/*
878 Adjust the casing of an identifier to match what the
879 database engine would have done to it.
880*/
881QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
882{
883 switch (m_defaultCase) {
884 case DefaultCase::Lower:
885 return identifier.toLower();
886 case DefaultCase::Upper:
887 return identifier.toUpper();
888 case DefaultCase::Mixed:
889 case DefaultCase::Sensitive:
890 break;
891 }
892 return identifier;
893}
894
895////////////////////////////////////////////////////////////////////////////
896
897QODBCResult::QODBCResult(const QODBCDriver *db)
898 : QSqlResult(*new QODBCResultPrivate(this, db))
899{
900}
901
903{
904 Q_D(QODBCResult);
905 if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) {
906 SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
907 if (r != SQL_SUCCESS)
908 qSqlWarning(("QODBCResult: Unable to free statement handle "_L1), d);
909 }
910}
911
912bool QODBCResult::reset (const QString& query)
913{
914 Q_D(QODBCResult);
915 setActive(false);
916 setAt(QSql::BeforeFirstRow);
917 d->rInf.clear();
918 d->fieldCache.clear();
919 d->fieldCacheIdx = 0;
920
921 // Always reallocate the statement handle - the statement attributes
922 // are not reset if SQLFreeStmt() is called which causes some problems.
923 SQLRETURN r;
924 if (d->hStmt && d->isStmtHandleValid()) {
925 r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
926 if (r != SQL_SUCCESS) {
927 qSqlWarning("QODBCResult::reset: Unable to free statement handle"_L1, d);
928 return false;
929 }
930 }
931 r = SQLAllocHandle(SQL_HANDLE_STMT,
932 d->dpDbc(),
933 &d->hStmt);
934 if (r != SQL_SUCCESS) {
935 qSqlWarning("QODBCResult::reset: Unable to allocate statement handle"_L1, d);
936 return false;
937 }
938
939 d->updateStmtHandleState();
940
941 // ODBCv3 version of setting a forward-only query
942 uint64_t sqlStmtVal = isForwardOnly() ? SQL_NONSCROLLABLE : SQL_SCROLLABLE;
943 r = SQLSetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, SQLPOINTER(sqlStmtVal), SQL_IS_UINTEGER);
944 if (!SQL_SUCCEEDED(r)) {
945 // ODBCv2 version of setting a forward-only query
946 sqlStmtVal = isForwardOnly() ? SQL_CURSOR_FORWARD_ONLY : SQL_CURSOR_STATIC;
947 r = SQLSetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_TYPE, SQLPOINTER(sqlStmtVal), SQL_IS_UINTEGER);
948 if (!SQL_SUCCEEDED(r)) {
949 setLastError(qMakeError(
950 QCoreApplication::translate("QODBCResult",
951 "QODBCResult::reset: Unable to set 'SQL_ATTR_CURSOR_TYPE' "
952 "as statement attribute. "
953 "Please check your ODBC driver configuration"),
954 QSqlError::StatementError, d));
955 return false;
956 }
957 }
958
959 {
960 auto encoded = toSQLTCHAR(query);
961 r = SQLExecDirect(d->hStmt,
962 encoded.data(),
963 SQLINTEGER(encoded.size()));
964 }
965 if (!SQL_SUCCEEDED(r) && r!= SQL_NO_DATA) {
966 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
967 "Unable to execute statement"), QSqlError::StatementError, d));
968 return false;
969 }
970
971 SQLULEN isScrollable = 0;
972 r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
973 if (SQL_SUCCEEDED(r))
974 setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
975
976 SQLSMALLINT count = 0;
977 SQLNumResultCols(d->hStmt, &count);
978 if (count) {
979 setSelect(true);
980 for (SQLSMALLINT i = 0; i < count; ++i)
981 d->rInf.append(qMakeFieldInfo(d, i));
982 d->fieldCache.resize(count);
983 } else {
984 setSelect(false);
985 }
986 setActive(true);
987
988 return true;
989}
990
991bool QODBCResult::fetch(int i)
992{
993 Q_D(QODBCResult);
994 if (!driver()->isOpen())
995 return false;
996
997 if (isForwardOnly() && i < at())
998 return false;
999 if (i == at())
1000 return true;
1001 d->clearValues();
1002 int actualIdx = i + 1;
1003 if (actualIdx <= 0) {
1004 setAt(QSql::BeforeFirstRow);
1005 return false;
1006 }
1007 SQLRETURN r;
1008 if (isForwardOnly()) {
1009 bool ok = true;
1010 while (ok && i > at())
1011 ok = fetchNext();
1012 return ok;
1013 } else {
1014 r = SQLFetchScroll(d->hStmt,
1015 SQL_FETCH_ABSOLUTE,
1016 actualIdx);
1017 }
1018 if (r != SQL_SUCCESS) {
1019 if (r != SQL_NO_DATA)
1020 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1021 "Unable to fetch"), QSqlError::ConnectionError, d));
1022 return false;
1023 }
1024 setAt(i);
1025 return true;
1026}
1027
1029{
1030 Q_D(QODBCResult);
1031 SQLRETURN r;
1032 d->clearValues();
1033
1034 if (d->hasSQLFetchScroll)
1035 r = SQLFetchScroll(d->hStmt,
1036 SQL_FETCH_NEXT,
1037 0);
1038 else
1039 r = SQLFetch(d->hStmt);
1040
1041 if (!SQL_SUCCEEDED(r)) {
1042 if (r != SQL_NO_DATA)
1043 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1044 "Unable to fetch next"), QSqlError::ConnectionError, d));
1045 return false;
1046 }
1047 setAt(at() + 1);
1048 return true;
1049}
1050
1052{
1053 Q_D(QODBCResult);
1054 if (isForwardOnly() && at() != QSql::BeforeFirstRow)
1055 return false;
1056 SQLRETURN r;
1057 d->clearValues();
1058 if (isForwardOnly()) {
1059 return fetchNext();
1060 }
1061 r = SQLFetchScroll(d->hStmt,
1062 SQL_FETCH_FIRST,
1063 0);
1064 if (r != SQL_SUCCESS) {
1065 if (r != SQL_NO_DATA)
1066 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1067 "Unable to fetch first"), QSqlError::ConnectionError, d));
1068 return false;
1069 }
1070 setAt(0);
1071 return true;
1072}
1073
1075{
1076 Q_D(QODBCResult);
1077 if (isForwardOnly())
1078 return false;
1079 SQLRETURN r;
1080 d->clearValues();
1081 r = SQLFetchScroll(d->hStmt,
1082 SQL_FETCH_PRIOR,
1083 0);
1084 if (r != SQL_SUCCESS) {
1085 if (r != SQL_NO_DATA)
1086 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1087 "Unable to fetch previous"), QSqlError::ConnectionError, d));
1088 return false;
1089 }
1090 setAt(at() - 1);
1091 return true;
1092}
1093
1095{
1096 Q_D(QODBCResult);
1097 SQLRETURN r;
1098 d->clearValues();
1099
1100 if (isForwardOnly()) {
1101 // cannot seek to last row in forwardOnly mode, so we have to use brute force
1102 int i = at();
1103 if (i == QSql::AfterLastRow)
1104 return false;
1105 if (i == QSql::BeforeFirstRow)
1106 i = 0;
1107 while (fetchNext())
1108 ++i;
1109 setAt(i);
1110 return true;
1111 }
1112
1113 r = SQLFetchScroll(d->hStmt,
1114 SQL_FETCH_LAST,
1115 0);
1116 if (r != SQL_SUCCESS) {
1117 if (r != SQL_NO_DATA)
1118 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1119 "Unable to fetch last"), QSqlError::ConnectionError, d));
1120 return false;
1121 }
1122 SQLULEN currRow = 0;
1123 r = SQLGetStmtAttr(d->hStmt,
1124 SQL_ROW_NUMBER,
1125 &currRow,
1126 SQL_IS_INTEGER,
1127 0);
1128 if (r != SQL_SUCCESS)
1129 return false;
1130 setAt(currRow-1);
1131 return true;
1132}
1133
1134QVariant QODBCResult::data(int field)
1135{
1136 Q_D(QODBCResult);
1137 if (field >= d->rInf.count() || field < 0) {
1138 qSqlWarning(("QODBCResult::data: column %1 out of range"_L1)
1139 .arg(QString::number(field)), d);
1140 return QVariant();
1141 }
1142 if (field < d->fieldCacheIdx)
1143 return d->fieldCache.at(field);
1144
1145 SQLRETURN r(0);
1146 SQLLEN lengthIndicator = 0;
1147
1148 for (int i = d->fieldCacheIdx; i <= field; ++i) {
1149 // some servers do not support fetching column n after we already
1150 // fetched column n+1, so cache all previous columns here
1151 const QSqlField info = d->rInf.field(i);
1152 const auto metaTypeId = info.metaType().id();
1153 switch (metaTypeId) {
1154 case QMetaType::LongLong:
1155 d->fieldCache[i] = qGetIntData<int64_t>(d->hStmt, i);
1156 break;
1157 case QMetaType::Int:
1158 d->fieldCache[i] = qGetIntData<int32_t>(d->hStmt, i);
1159 break;
1160 case QMetaType::Short:
1161 d->fieldCache[i] = qGetIntData<int16_t>(d->hStmt, i);
1162 break;
1163 case QMetaType::ULongLong:
1164 d->fieldCache[i] = qGetIntData<uint64_t>(d->hStmt, i);
1165 break;
1166 case QMetaType::UInt:
1167 d->fieldCache[i] = qGetIntData<uint32_t>(d->hStmt, i);
1168 break;
1169 case QMetaType::UShort:
1170 d->fieldCache[i] = qGetIntData<uint16_t>(d->hStmt, i);
1171 break;
1172 case QMetaType::QDate:
1173 DATE_STRUCT dbuf;
1174 r = SQLGetData(d->hStmt,
1175 i + 1,
1176 SQL_C_DATE,
1177 (SQLPOINTER)&dbuf,
1178 0,
1179 &lengthIndicator);
1180 if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
1181 d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
1182 else
1183 d->fieldCache[i] = QVariant(QMetaType::fromType<QDate>());
1184 break;
1185 case QMetaType::QTime:
1186 TIME_STRUCT tbuf;
1187 r = SQLGetData(d->hStmt,
1188 i + 1,
1189 SQL_C_TIME,
1190 (SQLPOINTER)&tbuf,
1191 0,
1192 &lengthIndicator);
1193 if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
1194 d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
1195 else
1196 d->fieldCache[i] = QVariant(QMetaType::fromType<QTime>());
1197 break;
1198 case QMetaType::QDateTime:
1199 TIMESTAMP_STRUCT dtbuf;
1200 r = SQLGetData(d->hStmt,
1201 i + 1,
1202 SQL_C_TIMESTAMP,
1203 (SQLPOINTER)&dtbuf,
1204 0,
1205 &lengthIndicator);
1206 if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
1207 d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
1208 QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
1209 else
1210 d->fieldCache[i] = QVariant(QMetaType::fromType<QDateTime>());
1211 break;
1212 case QMetaType::QByteArray:
1213 d->fieldCache[i] = qGetBinaryData(d->hStmt, i);
1214 break;
1215 case QMetaType::QString:
1216 d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode);
1217 break;
1218 case QMetaType::Float:
1219 case QMetaType::Double:
1220 switch (numericalPrecisionPolicy()) {
1221 case QSql::LowPrecisionInt32:
1222 d->fieldCache[i] = qGetIntData<int32_t>(d->hStmt, i);
1223 break;
1224 case QSql::LowPrecisionInt64:
1225 d->fieldCache[i] = qGetIntData<int64_t>(d->hStmt, i);
1226 break;
1227 case QSql::LowPrecisionDouble:
1228 if (metaTypeId == QMetaType::Float)
1229 d->fieldCache[i] = qGetFloatingPointData<float>(d->hStmt, i);
1230 else
1231 d->fieldCache[i] = qGetFloatingPointData<double>(d->hStmt, i);
1232 break;
1233 case QSql::HighPrecision:
1234 d->fieldCache[i] = qGetStringData(d->hStmt, i, -1, false);
1235 break;
1236 }
1237 break;
1238 default:
1239 d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false);
1240 break;
1241 }
1242 d->fieldCacheIdx = field + 1;
1243 }
1244 return d->fieldCache[field];
1245}
1246
1247bool QODBCResult::isNull(int field)
1248{
1249 Q_D(const QODBCResult);
1250 if (field < 0 || field >= d->fieldCache.size())
1251 return true;
1252 if (field >= d->fieldCacheIdx) {
1253 // since there is no good way to find out whether the value is NULL
1254 // without fetching the field we'll fetch it here.
1255 // (data() also sets the NULL flag)
1256 data(field);
1257 }
1258 return d->fieldCache.at(field).isNull();
1259}
1260
1262{
1263 return -1;
1264}
1265
1267{
1268 Q_D(QODBCResult);
1269 SQLLEN affectedRowCount = 0;
1270 SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
1271 if (r == SQL_SUCCESS)
1272 return affectedRowCount;
1273 qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d);
1274 return -1;
1275}
1276
1277bool QODBCResult::prepare(const QString& query)
1278{
1279 Q_D(QODBCResult);
1280 setActive(false);
1281 setAt(QSql::BeforeFirstRow);
1282 SQLRETURN r;
1283
1284 d->rInf.clear();
1285 d->fieldCache.clear();
1286 d->fieldCacheIdx = 0;
1287
1288 if (d->hStmt && d->isStmtHandleValid()) {
1289 r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
1290 if (r != SQL_SUCCESS) {
1291 qSqlWarning("QODBCResult::prepare: Unable to close statement"_L1, d);
1292 return false;
1293 }
1294 }
1295 r = SQLAllocHandle(SQL_HANDLE_STMT,
1296 d->dpDbc(),
1297 &d->hStmt);
1298 if (r != SQL_SUCCESS) {
1299 qSqlWarning("QODBCResult::prepare: Unable to allocate statement handle"_L1, d);
1300 return false;
1301 }
1302
1303 d->updateStmtHandleState();
1304
1305 if (isForwardOnly()) {
1306 r = SQLSetStmtAttr(d->hStmt,
1307 SQL_ATTR_CURSOR_TYPE,
1308 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
1309 SQL_IS_UINTEGER);
1310 } else {
1311 r = SQLSetStmtAttr(d->hStmt,
1312 SQL_ATTR_CURSOR_TYPE,
1313 (SQLPOINTER)SQL_CURSOR_STATIC,
1314 SQL_IS_UINTEGER);
1315 }
1316 if (!SQL_SUCCEEDED(r)) {
1317 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1318 "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
1319 "Please check your ODBC driver configuration"), QSqlError::StatementError, d));
1320 return false;
1321 }
1322
1323 {
1324 auto encoded = toSQLTCHAR(query);
1325 r = SQLPrepare(d->hStmt,
1326 encoded.data(),
1327 SQLINTEGER(encoded.size()));
1328 }
1329
1330 if (r != SQL_SUCCESS) {
1331 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1332 "Unable to prepare statement"), QSqlError::StatementError, d));
1333 return false;
1334 }
1335 return true;
1336}
1337
1339{
1340 Q_D(QODBCResult);
1341 setActive(false);
1342 setAt(QSql::BeforeFirstRow);
1343 d->rInf.clear();
1344 d->fieldCache.clear();
1345 d->fieldCacheIdx = 0;
1346
1347 if (!d->hStmt) {
1348 qSqlWarning("QODBCResult::exec: No statement handle available"_L1, d);
1349 return false;
1350 }
1351
1352 if (isSelect())
1353 SQLCloseCursor(d->hStmt);
1354
1355 QVariantList &values = boundValues();
1356 QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter()
1357 QVarLengthArray<SQLLEN, 32> indicators(values.count(), 0);
1358
1359 // bind parameters - only positional binding allowed
1360 SQLRETURN r;
1361 for (qsizetype i = 0; i < values.count(); ++i) {
1362 if (bindValueType(i) & QSql::Out)
1363 values[i].detach();
1364 const QVariant &val = values.at(i);
1365 SQLLEN *ind = &indicators[i];
1366 if (QSqlResultPrivate::isVariantNull(val))
1367 *ind = SQL_NULL_DATA;
1368 switch (val.typeId()) {
1369 case QMetaType::QDate: {
1370 QByteArray &ba = tmpStorage[i];
1371 ba.resize(sizeof(DATE_STRUCT));
1372 DATE_STRUCT *dt = (DATE_STRUCT *)const_cast<char *>(ba.constData());
1373 QDate qdt = val.toDate();
1374 dt->year = qdt.year();
1375 dt->month = qdt.month();
1376 dt->day = qdt.day();
1377 r = SQLBindParameter(d->hStmt,
1378 i + 1,
1379 qParamType[bindValueType(i) & QSql::InOut],
1380 SQL_C_DATE,
1381 SQL_DATE,
1382 0,
1383 0,
1384 (void *) dt,
1385 0,
1386 *ind == SQL_NULL_DATA ? ind : NULL);
1387 break; }
1388 case QMetaType::QTime: {
1389 QByteArray &ba = tmpStorage[i];
1390 ba.resize(sizeof(TIME_STRUCT));
1391 TIME_STRUCT *dt = (TIME_STRUCT *)const_cast<char *>(ba.constData());
1392 QTime qdt = val.toTime();
1393 dt->hour = qdt.hour();
1394 dt->minute = qdt.minute();
1395 dt->second = qdt.second();
1396 r = SQLBindParameter(d->hStmt,
1397 i + 1,
1398 qParamType[bindValueType(i) & QSql::InOut],
1399 SQL_C_TIME,
1400 SQL_TIME,
1401 0,
1402 0,
1403 (void *) dt,
1404 0,
1405 *ind == SQL_NULL_DATA ? ind : NULL);
1406 break; }
1407 case QMetaType::QDateTime: {
1408 QByteArray &ba = tmpStorage[i];
1409 ba.resize(sizeof(TIMESTAMP_STRUCT));
1410 TIMESTAMP_STRUCT *dt = reinterpret_cast<TIMESTAMP_STRUCT *>(const_cast<char *>(ba.constData()));
1411 const QDateTime qdt = val.toDateTime();
1412 const QDate qdate = qdt.date();
1413 const QTime qtime = qdt.time();
1414 dt->year = qdate.year();
1415 dt->month = qdate.month();
1416 dt->day = qdate.day();
1417 dt->hour = qtime.hour();
1418 dt->minute = qtime.minute();
1419 dt->second = qtime.second();
1420 // (20 includes a separating period)
1421 const int precision = d->drv_d_func()->datetimePrecision - 20;
1422 if (precision <= 0) {
1423 dt->fraction = 0;
1424 } else {
1425 dt->fraction = qtime.msec() * 1000000;
1426
1427 // (How many leading digits do we want to keep? With SQL Server 2005, this should be 3: 123000000)
1428 int keep = (int)qPow(10.0, 9 - qMin(9, precision));
1429 dt->fraction = (dt->fraction / keep) * keep;
1430 }
1431
1432 r = SQLBindParameter(d->hStmt,
1433 i + 1,
1434 qParamType[bindValueType(i) & QSql::InOut],
1435 SQL_C_TIMESTAMP,
1436 SQL_TIMESTAMP,
1437 d->drv_d_func()->datetimePrecision,
1438 precision,
1439 (void *) dt,
1440 0,
1441 *ind == SQL_NULL_DATA ? ind : NULL);
1442 break; }
1443 case QMetaType::Int:
1444 r = SQLBindParameter(d->hStmt,
1445 i + 1,
1446 qParamType[bindValueType(i) & QSql::InOut],
1447 SQL_C_SLONG,
1448 SQL_INTEGER,
1449 0,
1450 0,
1451 const_cast<void *>(val.constData()),
1452 0,
1453 *ind == SQL_NULL_DATA ? ind : NULL);
1454 break;
1455 case QMetaType::UInt:
1456 r = SQLBindParameter(d->hStmt,
1457 i + 1,
1458 qParamType[bindValueType(i) & QSql::InOut],
1459 SQL_C_ULONG,
1460 SQL_NUMERIC,
1461 15,
1462 0,
1463 const_cast<void *>(val.constData()),
1464 0,
1465 *ind == SQL_NULL_DATA ? ind : NULL);
1466 break;
1467 case QMetaType::Short:
1468 r = SQLBindParameter(d->hStmt,
1469 i + 1,
1470 qParamType[bindValueType(i) & QSql::InOut],
1471 SQL_C_SSHORT,
1472 SQL_SMALLINT,
1473 0,
1474 0,
1475 const_cast<void *>(val.constData()),
1476 0,
1477 *ind == SQL_NULL_DATA ? ind : NULL);
1478 break;
1479 case QMetaType::UShort:
1480 r = SQLBindParameter(d->hStmt,
1481 i + 1,
1482 qParamType[bindValueType(i) & QSql::InOut],
1483 SQL_C_USHORT,
1484 SQL_NUMERIC,
1485 15,
1486 0,
1487 const_cast<void *>(val.constData()),
1488 0,
1489 *ind == SQL_NULL_DATA ? ind : NULL);
1490 break;
1491 case QMetaType::Double:
1492 r = SQLBindParameter(d->hStmt,
1493 i + 1,
1494 qParamType[bindValueType(i) & QSql::InOut],
1495 SQL_C_DOUBLE,
1496 SQL_DOUBLE,
1497 0,
1498 0,
1499 const_cast<void *>(val.constData()),
1500 0,
1501 *ind == SQL_NULL_DATA ? ind : NULL);
1502 break;
1503 case QMetaType::Float:
1504 r = SQLBindParameter(d->hStmt,
1505 i + 1,
1506 qParamType[bindValueType(i) & QSql::InOut],
1507 SQL_C_FLOAT,
1508 SQL_REAL,
1509 0,
1510 0,
1511 const_cast<void *>(val.constData()),
1512 0,
1513 *ind == SQL_NULL_DATA ? ind : NULL);
1514 break;
1515 case QMetaType::LongLong:
1516 r = SQLBindParameter(d->hStmt,
1517 i + 1,
1518 qParamType[bindValueType(i) & QSql::InOut],
1519 SQL_C_SBIGINT,
1520 SQL_BIGINT,
1521 0,
1522 0,
1523 const_cast<void *>(val.constData()),
1524 0,
1525 *ind == SQL_NULL_DATA ? ind : NULL);
1526 break;
1527 case QMetaType::ULongLong:
1528 r = SQLBindParameter(d->hStmt,
1529 i + 1,
1530 qParamType[bindValueType(i) & QSql::InOut],
1531 SQL_C_UBIGINT,
1532 SQL_BIGINT,
1533 0,
1534 0,
1535 const_cast<void *>(val.constData()),
1536 0,
1537 *ind == SQL_NULL_DATA ? ind : NULL);
1538 break;
1539 case QMetaType::QByteArray:
1540 if (*ind != SQL_NULL_DATA) {
1541 *ind = val.toByteArray().size();
1542 }
1543 r = SQLBindParameter(d->hStmt,
1544 i + 1,
1545 qParamType[bindValueType(i) & QSql::InOut],
1546 SQL_C_BINARY,
1547 SQL_LONGVARBINARY,
1548 val.toByteArray().size(),
1549 0,
1550 const_cast<char *>(val.toByteArray().constData()),
1551 val.toByteArray().size(),
1552 ind);
1553 break;
1554 case QMetaType::Bool:
1555 r = SQLBindParameter(d->hStmt,
1556 i + 1,
1557 qParamType[bindValueType(i) & QSql::InOut],
1558 SQL_C_BIT,
1559 SQL_BIT,
1560 0,
1561 0,
1562 const_cast<void *>(val.constData()),
1563 0,
1564 *ind == SQL_NULL_DATA ? ind : NULL);
1565 break;
1566 case QMetaType::QString:
1567 if (d->unicode) {
1568 QByteArray &ba = tmpStorage[i];
1569 {
1570 const auto encoded = toSQLTCHAR(val.toString());
1571 ba = QByteArray(reinterpret_cast<const char *>(encoded.data()),
1572 encoded.size() * sizeof(SQLTCHAR));
1573 }
1574
1575 if (*ind != SQL_NULL_DATA)
1576 *ind = ba.size();
1577
1578 if (bindValueType(i) & QSql::Out) {
1579 r = SQLBindParameter(d->hStmt,
1580 i + 1,
1581 qParamType[bindValueType(i) & QSql::InOut],
1582 SQL_C_TCHAR,
1583 ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
1584 0, // god knows... don't change this!
1585 0,
1586 const_cast<char *>(ba.constData()), // don't detach
1587 ba.size(),
1588 ind);
1589 break;
1590 }
1591 r = SQLBindParameter(d->hStmt,
1592 i + 1,
1593 qParamType[bindValueType(i) & QSql::InOut],
1594 SQL_C_TCHAR,
1595 ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
1596 ba.size(),
1597 0,
1598 const_cast<char *>(ba.constData()), // don't detach
1599 ba.size(),
1600 ind);
1601 break;
1602 }
1603 else
1604 {
1605 QByteArray &str = tmpStorage[i];
1606 str = val.toString().toUtf8();
1607 if (*ind != SQL_NULL_DATA)
1608 *ind = str.length();
1609 int strSize = str.length();
1610
1611 r = SQLBindParameter(d->hStmt,
1612 i + 1,
1613 qParamType[bindValueType(i) & QSql::InOut],
1614 SQL_C_CHAR,
1615 strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR,
1616 strSize,
1617 0,
1618 const_cast<char *>(str.constData()),
1619 strSize,
1620 ind);
1621 break;
1622 }
1623 Q_FALLTHROUGH();
1624 default: {
1625 QByteArray &ba = tmpStorage[i];
1626 if (*ind != SQL_NULL_DATA)
1627 *ind = ba.size();
1628 r = SQLBindParameter(d->hStmt,
1629 i + 1,
1630 qParamType[bindValueType(i) & QSql::InOut],
1631 SQL_C_BINARY,
1632 SQL_VARBINARY,
1633 ba.length() + 1,
1634 0,
1635 const_cast<char *>(ba.constData()),
1636 ba.length() + 1,
1637 ind);
1638 break; }
1639 }
1640 if (r != SQL_SUCCESS) {
1641 qSqlWarning("QODBCResult::exec: unable to bind variable:"_L1, d);
1642 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1643 "Unable to bind variable"), QSqlError::StatementError, d));
1644 return false;
1645 }
1646 }
1647 r = SQLExecute(d->hStmt);
1648 if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
1649 qSqlWarning("QODBCResult::exec: Unable to execute statement:"_L1, d);
1650 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1651 "Unable to execute statement"), QSqlError::StatementError, d));
1652 return false;
1653 }
1654
1655 SQLULEN isScrollable = 0;
1656 r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
1657 if (SQL_SUCCEEDED(r))
1658 setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
1659
1660 SQLSMALLINT count = 0;
1661 SQLNumResultCols(d->hStmt, &count);
1662 if (count) {
1663 setSelect(true);
1664 for (SQLSMALLINT i = 0; i < count; ++i)
1665 d->rInf.append(qMakeFieldInfo(d, i));
1666 d->fieldCache.resize(count);
1667 } else {
1668 setSelect(false);
1669 }
1670 setActive(true);
1671
1672
1673 //get out parameters
1674 if (!hasOutValues())
1675 return true;
1676
1677 for (qsizetype i = 0; i < values.count(); ++i) {
1678 switch (values.at(i).typeId()) {
1679 case QMetaType::QDate: {
1680 DATE_STRUCT ds = *((DATE_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
1681 values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
1682 break; }
1683 case QMetaType::QTime: {
1684 TIME_STRUCT dt = *((TIME_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
1685 values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
1686 break; }
1687 case QMetaType::QDateTime: {
1688 TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*)
1689 const_cast<char *>(tmpStorage.at(i).constData()));
1690 values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
1691 QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
1692 break; }
1693 case QMetaType::Bool:
1694 case QMetaType::Short:
1695 case QMetaType::UShort:
1696 case QMetaType::Int:
1697 case QMetaType::UInt:
1698 case QMetaType::Float:
1699 case QMetaType::Double:
1700 case QMetaType::QByteArray:
1701 case QMetaType::LongLong:
1702 case QMetaType::ULongLong:
1703 //nothing to do
1704 break;
1705 case QMetaType::QString:
1706 if (d->unicode) {
1707 if (bindValueType(i) & QSql::Out) {
1708 const QByteArray &bytes = tmpStorage.at(i);
1709 const auto strSize = bytes.size() / sizeof(SQLTCHAR);
1710 QVarLengthArray<SQLTCHAR> string(strSize);
1711 memcpy(string.data(), bytes.data(), strSize * sizeof(SQLTCHAR));
1712 values[i] = fromSQLTCHAR(string);
1713 }
1714 break;
1715 }
1716 Q_FALLTHROUGH();
1717 default: {
1718 if (bindValueType(i) & QSql::Out)
1719 values[i] = tmpStorage.at(i);
1720 break; }
1721 }
1722 if (indicators[i] == SQL_NULL_DATA)
1723 values[i] = QVariant(values[i].metaType());
1724 }
1725 return true;
1726}
1727
1729{
1730 Q_D(const QODBCResult);
1731 if (!isActive() || !isSelect())
1732 return QSqlRecord();
1733 return d->rInf;
1734}
1735
1736QVariant QODBCResult::lastInsertId() const
1737{
1738 Q_D(const QODBCResult);
1739 QString sql;
1740
1741 switch (driver()->dbmsType()) {
1742 case QSqlDriver::MSSqlServer:
1743 case QSqlDriver::Sybase:
1744 sql = "SELECT @@IDENTITY;"_L1;
1745 break;
1746 case QSqlDriver::MySqlServer:
1747 sql = "SELECT LAST_INSERT_ID();"_L1;
1748 break;
1749 case QSqlDriver::PostgreSQL:
1750 sql = "SELECT lastval();"_L1;
1751 break;
1752 default:
1753 break;
1754 }
1755
1756 if (!sql.isEmpty()) {
1757 QSqlQuery qry(driver()->createResult());
1758 if (qry.exec(sql) && qry.next())
1759 return qry.value(0);
1760
1761 qSqlWarning("QODBCResult::lastInsertId: Unable to get lastInsertId"_L1, d);
1762 } else {
1763 qSqlWarning("QODBCResult::lastInsertId: not implemented for this DBMS"_L1, d);
1764 }
1765
1766 return QVariant();
1767}
1768
1769QVariant QODBCResult::handle() const
1770{
1771 Q_D(const QODBCResult);
1772 return QVariant(QMetaType::fromType<SQLHANDLE>(), &d->hStmt);
1773}
1774
1776{
1777 Q_D(QODBCResult);
1778 setActive(false);
1779 setAt(QSql::BeforeFirstRow);
1780 d->rInf.clear();
1781 d->fieldCache.clear();
1782 d->fieldCacheIdx = 0;
1783 setSelect(false);
1784
1785 SQLRETURN r = SQLMoreResults(d->hStmt);
1786 if (r != SQL_SUCCESS) {
1787 if (r == SQL_SUCCESS_WITH_INFO) {
1788 qSqlWarning("QODBCResult::nextResult:"_L1, d);
1789 } else {
1790 if (r != SQL_NO_DATA)
1791 setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
1792 "Unable to fetch last"), QSqlError::ConnectionError, d));
1793 return false;
1794 }
1795 }
1796
1797 SQLSMALLINT count = 0;
1798 SQLNumResultCols(d->hStmt, &count);
1799 if (count) {
1800 setSelect(true);
1801 for (SQLSMALLINT i = 0; i < count; ++i) {
1802 d->rInf.append(qMakeFieldInfo(d, i));
1803 }
1804 d->fieldCache.resize(count);
1805 } else {
1806 setSelect(false);
1807 }
1808 setActive(true);
1809
1810 return true;
1811}
1812
1813void QODBCResult::virtual_hook(int id, void *data)
1814{
1815 QSqlResult::virtual_hook(id, data);
1816}
1817
1819{
1820 Q_D(QODBCResult);
1821 if (d->hStmt)
1822 SQLCloseCursor(d->hStmt);
1823}
1824
1825////////////////////////////////////////
1826
1827
1828QODBCDriver::QODBCDriver(QObject *parent)
1829 : QSqlDriver(*new QODBCDriverPrivate, parent)
1830{
1831}
1832
1833QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject *parent)
1834 : QSqlDriver(*new QODBCDriverPrivate, parent)
1835{
1836 Q_D(QODBCDriver);
1837 d->hEnv = env;
1838 d->hDbc = con;
1839 if (env && con) {
1840 setOpen(true);
1841 setOpenError(false);
1842 }
1843}
1844
1845QODBCDriver::~QODBCDriver()
1846{
1847 cleanup();
1848}
1849
1850bool QODBCDriver::hasFeature(DriverFeature f) const
1851{
1852 Q_D(const QODBCDriver);
1853 switch (f) {
1854 case Transactions: {
1855 if (!d->hDbc)
1856 return false;
1857 SQLUSMALLINT txn;
1858 SQLSMALLINT t;
1859 int r = SQLGetInfo(d->hDbc,
1860 (SQLUSMALLINT)SQL_TXN_CAPABLE,
1861 &txn,
1862 sizeof(txn),
1863 &t);
1864 if (r != SQL_SUCCESS || txn == SQL_TC_NONE)
1865 return false;
1866 else
1867 return true;
1868 }
1869 case Unicode:
1870 return d->unicode;
1871 case PreparedQueries:
1872 case PositionalPlaceholders:
1873 case FinishQuery:
1874 case LowPrecisionNumbers:
1875 return true;
1876 case QuerySize:
1877 case NamedPlaceholders:
1878 case BatchOperations:
1879 case SimpleLocking:
1880 case EventNotifications:
1881 case CancelQuery:
1882 return false;
1883 case LastInsertId:
1884 return (d->dbmsType == MSSqlServer)
1885 || (d->dbmsType == Sybase)
1886 || (d->dbmsType == MySqlServer)
1887 || (d->dbmsType == PostgreSQL);
1888 case MultipleResultSets:
1889 return d->hasMultiResultSets;
1890 case BLOB: {
1891 if (d->dbmsType == MySqlServer)
1892 return true;
1893 else
1894 return false;
1895 }
1896 }
1897 return false;
1898}
1899
1900bool QODBCDriver::open(const QString & db,
1901 const QString & user,
1902 const QString & password,
1903 const QString &,
1904 int,
1905 const QString& connOpts)
1906{
1907 Q_D(QODBCDriver);
1908 if (isOpen())
1909 close();
1910 SQLRETURN r;
1911 r = SQLAllocHandle(SQL_HANDLE_ENV,
1912 SQL_NULL_HANDLE,
1913 &d->hEnv);
1914 if (!SQL_SUCCEEDED(r)) {
1915 qSqlWarning("QODBCDriver::open: Unable to allocate environment"_L1, d);
1916 setOpenError(true);
1917 return false;
1918 }
1919 r = SQLSetEnvAttr(d->hEnv,
1920 SQL_ATTR_ODBC_VERSION,
1921 (SQLPOINTER)qGetODBCVersion(connOpts),
1922 SQL_IS_UINTEGER);
1923 r = SQLAllocHandle(SQL_HANDLE_DBC,
1924 d->hEnv,
1925 &d->hDbc);
1926 if (!SQL_SUCCEEDED(r)) {
1927 qSqlWarning("QODBCDriver::open: Unable to allocate connection"_L1, d);
1928 setOpenError(true);
1929 cleanup();
1930 return false;
1931 }
1932
1933 if (!d->setConnectionOptions(connOpts)) {
1934 cleanup();
1935 return false;
1936 }
1937
1938 // Create the connection string
1939 QString connQStr;
1940 // support the "DRIVER={SQL SERVER};SERVER=blah" syntax
1941 if (db.contains(".dsn"_L1, Qt::CaseInsensitive))
1942 connQStr = "FILEDSN="_L1 + db;
1943 else if (db.contains("DRIVER="_L1, Qt::CaseInsensitive)
1944 || db.contains("SERVER="_L1, Qt::CaseInsensitive))
1945 connQStr = db;
1946 else
1947 connQStr = "DSN="_L1 + db;
1948
1949 const auto escapePassword = [](const QString &arg) -> QString {
1950 QString ret;
1951 ret.reserve(arg.size() + 8);
1952 for (const auto c : arg) {
1953 // we percent-encode ascii chars which are not a letter
1954 // or digit. For everything >= 0x80 we can't do much as
1955 // we don't know how the odbc layer and odbc driver encodes
1956 // and handles the string internally
1957 if (c.isLetterOrNumber())
1958 ret += c;
1959 else if (c.unicode() < 128)
1960 ret += QString::asprintf("%%%02x", c.unicode());
1961 else
1962 ret += c; // good luck
1963 }
1964 return ret;
1965 };
1966 if (!user.isEmpty())
1967 connQStr += ";UID="_L1 + user;
1968 if (!password.isEmpty())
1969 connQStr += ";PWD="_L1 + escapePassword(password);
1970
1971 SQLSMALLINT cb;
1972 QVarLengthArray<SQLTCHAR, 1024> connOut(1024);
1973 {
1974 auto encoded = toSQLTCHAR(connQStr);
1975 r = SQLDriverConnect(d->hDbc,
1976 nullptr,
1977 encoded.data(), SQLSMALLINT(encoded.size()),
1978 connOut.data(), SQLSMALLINT(connOut.size()),
1979 &cb,
1980 /*SQL_DRIVER_NOPROMPT*/0);
1981 }
1982
1983 if (!SQL_SUCCEEDED(r)) {
1984 setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
1985 setOpenError(true);
1986 cleanup();
1987 return false;
1988 }
1989
1990 if (!d->checkDriver()) {
1991 setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
1992 "functionality required"), QSqlError::ConnectionError, d));
1993 setOpenError(true);
1994 cleanup();
1995 return false;
1996 }
1997
1998 d->checkUnicode();
1999 d->checkSchemaUsage();
2000 d->checkDBMS();
2001 d->checkHasSQLFetchScroll();
2002 d->checkHasMultiResults();
2003 d->checkDateTimePrecision();
2004 d->checkDefaultCase();
2005 setOpen(true);
2006 setOpenError(false);
2007 if (d->dbmsType == MSSqlServer) {
2008 QSqlQuery i(createResult());
2009 i.exec("SET QUOTED_IDENTIFIER ON"_L1);
2010 }
2011 return true;
2012}
2013
2014void QODBCDriver::close()
2015{
2016 cleanup();
2017 setOpen(false);
2018 setOpenError(false);
2019}
2020
2021void QODBCDriver::cleanup()
2022{
2023 Q_D(QODBCDriver);
2024 SQLRETURN r;
2025
2026 if (d->hDbc) {
2027 // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
2028 if (isOpen()) {
2029 r = SQLDisconnect(d->hDbc);
2030 if (r != SQL_SUCCESS)
2031 qSqlWarning("QODBCDriver::disconnect: Unable to disconnect datasource"_L1, d);
2032 else
2033 d->disconnectCount++;
2034 }
2035
2036 r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
2037 if (r != SQL_SUCCESS)
2038 qSqlWarning("QODBCDriver::cleanup: Unable to free connection handle"_L1, d);
2039 d->hDbc = 0;
2040 }
2041
2042 if (d->hEnv) {
2043 r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
2044 if (r != SQL_SUCCESS)
2045 qSqlWarning("QODBCDriver::cleanup: Unable to free environment handle"_L1, d);
2046 d->hEnv = 0;
2047 }
2048}
2049
2050// checks whether the server can return char, varchar and longvarchar
2051// as two byte unicode characters
2053{
2054 SQLRETURN r;
2055 SQLUINTEGER fFunc;
2056
2057 unicode = false;
2058 r = SQLGetInfo(hDbc,
2059 SQL_CONVERT_CHAR,
2060 (SQLPOINTER)&fFunc,
2061 sizeof(fFunc),
2062 NULL);
2063 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WCHAR)) {
2064 unicode = true;
2065 return;
2066 }
2067
2068 r = SQLGetInfo(hDbc,
2069 SQL_CONVERT_VARCHAR,
2070 (SQLPOINTER)&fFunc,
2071 sizeof(fFunc),
2072 NULL);
2073 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WVARCHAR)) {
2074 unicode = true;
2075 return;
2076 }
2077
2078 r = SQLGetInfo(hDbc,
2079 SQL_CONVERT_LONGVARCHAR,
2080 (SQLPOINTER)&fFunc,
2081 sizeof(fFunc),
2082 NULL);
2083 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
2084 unicode = true;
2085 return;
2086 }
2087
2088 SqlStmtHandle hStmt(hDbc);
2089 // for databases which do not return something useful in SQLGetInfo and are picky about a
2090 // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle
2091 const std::array<QStringView, 3> statements = {
2092 u"select 'test'",
2093 u"values('test')",
2094 u"select 'test' from dual",
2095 };
2096 for (const auto &statement : statements) {
2097 auto encoded = toSQLTCHAR(statement);
2098 r = SQLExecDirect(hStmt.handle(), encoded.data(), SQLINTEGER(encoded.size()));
2099 if (r == SQL_SUCCESS)
2100 break;
2101 }
2102 if (r == SQL_SUCCESS) {
2103 r = SQLFetch(hStmt.handle());
2104 if (r == SQL_SUCCESS) {
2105 QVarLengthArray<SQLWCHAR, 10> buffer(10);
2106 r = SQLGetData(hStmt.handle(), 1, SQL_C_WCHAR, buffer.data(),
2107 buffer.size() * sizeof(SQLWCHAR), NULL);
2108 if (r == SQL_SUCCESS && fromSQLTCHAR(buffer) == "test"_L1) {
2109 unicode = true;
2110 }
2111 }
2112 }
2113}
2114
2116{
2117#ifdef ODBC_CHECK_DRIVER
2118 static constexpr SQLUSMALLINT reqFunc[] = {
2119 SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
2120 SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
2121 SQL_API_SQLGETINFO, SQL_API_SQLTABLES
2122 };
2123
2124 // these functions are optional
2125 static constexpr SQLUSMALLINT optFunc[] = {
2126 SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT
2127 };
2128
2129 SQLRETURN r;
2130 SQLUSMALLINT sup;
2131
2132 // check the required functions
2133 for (const SQLUSMALLINT func : reqFunc) {
2134
2135 r = SQLGetFunctions(hDbc, func, &sup);
2136
2137 if (r != SQL_SUCCESS) {
2138 qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
2139 return false;
2140 }
2141 if (sup == SQL_FALSE) {
2142 qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support all needed "
2143 "functionality (func id %1).\nPlease look at the Qt SQL Module "
2144 "Driver documentation for more information."_L1)
2145 .arg(QString::number(func)), this);
2146 return false;
2147 }
2148 }
2149
2150 // these functions are optional and just generate a warning
2151 for (const SQLUSMALLINT func : optFunc) {
2152
2153 r = SQLGetFunctions(hDbc, func, &sup);
2154
2155 if (r != SQL_SUCCESS) {
2156 qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
2157 return false;
2158 }
2159 if (sup == SQL_FALSE) {
2160 qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support some "
2161 "non-critical functions (func id %1)."_L1)
2162 .arg(QString::number(func)), this);
2163 return true;
2164 }
2165 }
2166#endif //ODBC_CHECK_DRIVER
2167
2168 return true;
2169}
2170
2172{
2173 SQLRETURN r;
2174 SQLUINTEGER val;
2175
2176 r = SQLGetInfo(hDbc,
2177 SQL_SCHEMA_USAGE,
2178 (SQLPOINTER) &val,
2179 sizeof(val),
2180 NULL);
2181 if (SQL_SUCCEEDED(r))
2182 useSchema = (val != 0);
2183}
2184
2186{
2187 SQLRETURN r;
2188 QVarLengthArray<SQLTCHAR, 200> serverString(200);
2189 SQLSMALLINT t;
2190
2191 r = SQLGetInfo(hDbc,
2192 SQL_DBMS_NAME,
2193 serverString.data(),
2194 SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
2195 &t);
2196 if (SQL_SUCCEEDED(r)) {
2197 const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
2198 if (serverType.contains("PostgreSQL"_L1, Qt::CaseInsensitive))
2199 dbmsType = QSqlDriver::PostgreSQL;
2200 else if (serverType.contains("Oracle"_L1, Qt::CaseInsensitive))
2201 dbmsType = QSqlDriver::Oracle;
2202 else if (serverType.contains("MySql"_L1, Qt::CaseInsensitive))
2203 dbmsType = QSqlDriver::MySqlServer;
2204 else if (serverType.contains("Microsoft SQL Server"_L1, Qt::CaseInsensitive))
2205 dbmsType = QSqlDriver::MSSqlServer;
2206 else if (serverType.contains("Sybase"_L1, Qt::CaseInsensitive))
2207 dbmsType = QSqlDriver::Sybase;
2208 }
2209 r = SQLGetInfo(hDbc,
2210 SQL_DRIVER_NAME,
2211 serverString.data(),
2212 SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
2213 &t);
2214 if (SQL_SUCCEEDED(r)) {
2215 const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
2216 isFreeTDSDriver = serverType.contains("tdsodbc"_L1, Qt::CaseInsensitive);
2218 }
2219}
2220
2222{
2223 SQLUSMALLINT sup;
2224 SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
2225 if ((!SQL_SUCCEEDED(r)) || sup != SQL_TRUE) {
2226 hasSQLFetchScroll = false;
2227 qSqlWarning("QODBCDriver::checkHasSQLFetchScroll: Driver doesn't support "
2228 "scrollable result sets, use forward only mode for queries"_L1, this);
2229 }
2230}
2231
2233{
2234 QVarLengthArray<SQLTCHAR, 2> driverResponse(2);
2235 SQLSMALLINT length;
2236 SQLRETURN r = SQLGetInfo(hDbc,
2237 SQL_MULT_RESULT_SETS,
2238 driverResponse.data(),
2239 SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
2240 &length);
2241 if (SQL_SUCCEEDED(r))
2242 hasMultiResultSets = fromSQLTCHAR(driverResponse, length / sizeof(SQLTCHAR)).startsWith(u'Y');
2243}
2244
2246{
2247 SQLINTEGER columnSize;
2248 SqlStmtHandle hStmt(hDbc);
2249
2250 if (!hStmt.isValid())
2251 return;
2252
2253 SQLRETURN r = SQLGetTypeInfo(hStmt.handle(), SQL_TIMESTAMP);
2254 if (SQL_SUCCEEDED(r)) {
2255 r = SQLFetch(hStmt.handle());
2256 if (SQL_SUCCEEDED(r)) {
2257 if (SQLGetData(hStmt.handle(), 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS)
2258 datetimePrecision = (int)columnSize;
2259 }
2260 }
2261}
2262
2263QSqlResult *QODBCDriver::createResult() const
2264{
2265 return new QODBCResult(this);
2266}
2267
2268bool QODBCDriver::beginTransaction()
2269{
2270 Q_D(QODBCDriver);
2271 if (!isOpen()) {
2272 qSqlWarning("QODBCDriver::beginTransaction: Database not open"_L1, d);
2273 return false;
2274 }
2275 SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
2276 SQLRETURN r = SQLSetConnectAttr(d->hDbc,
2277 SQL_ATTR_AUTOCOMMIT,
2278 (SQLPOINTER)size_t(ac),
2279 sizeof(ac));
2280 if (r != SQL_SUCCESS) {
2281 setLastError(qMakeError(tr("Unable to disable autocommit"),
2282 QSqlError::TransactionError, d));
2283 return false;
2284 }
2285 return true;
2286}
2287
2288bool QODBCDriver::commitTransaction()
2289{
2290 Q_D(QODBCDriver);
2291 if (!isOpen()) {
2292 qSqlWarning("QODBCDriver::commitTransaction: Database not open"_L1, d);
2293 return false;
2294 }
2295 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
2296 d->hDbc,
2297 SQL_COMMIT);
2298 if (r != SQL_SUCCESS) {
2299 setLastError(qMakeError(tr("Unable to commit transaction"),
2300 QSqlError::TransactionError, d));
2301 return false;
2302 }
2303 return endTrans();
2304}
2305
2306bool QODBCDriver::rollbackTransaction()
2307{
2308 Q_D(QODBCDriver);
2309 if (!isOpen()) {
2310 qSqlWarning("QODBCDriver::rollbackTransaction: Database not open"_L1, d);
2311 return false;
2312 }
2313 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
2314 d->hDbc,
2315 SQL_ROLLBACK);
2316 if (r != SQL_SUCCESS) {
2317 setLastError(qMakeError(tr("Unable to rollback transaction"),
2318 QSqlError::TransactionError, d));
2319 return false;
2320 }
2321 return endTrans();
2322}
2323
2324bool QODBCDriver::endTrans()
2325{
2326 Q_D(QODBCDriver);
2327 SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
2328 SQLRETURN r = SQLSetConnectAttr(d->hDbc,
2329 SQL_ATTR_AUTOCOMMIT,
2330 (SQLPOINTER)size_t(ac),
2331 sizeof(ac));
2332 if (r != SQL_SUCCESS) {
2333 setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d));
2334 return false;
2335 }
2336 return true;
2337}
2338
2339QStringList QODBCDriver::tables(QSql::TableType type) const
2340{
2341 Q_D(const QODBCDriver);
2342 QStringList tl;
2343 if (!isOpen())
2344 return tl;
2345
2346 SqlStmtHandle hStmt(d->hDbc);
2347 if (!hStmt.isValid()) {
2348 qSqlWarning("QODBCDriver::tables: Unable to allocate handle"_L1, d);
2349 return tl;
2350 }
2351 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2352 SQL_ATTR_CURSOR_TYPE,
2353 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2354 SQL_IS_UINTEGER);
2355 QStringList tableType;
2356 if (type & QSql::Tables)
2357 tableType += "TABLE"_L1;
2358 if (type & QSql::Views)
2359 tableType += "VIEW"_L1;
2360 if (type & QSql::SystemTables)
2361 tableType += "SYSTEM TABLE"_L1;
2362 if (tableType.isEmpty())
2363 return tl;
2364
2365 {
2366 auto joinedTableTypeString = toSQLTCHAR(tableType.join(u','));
2367
2368 r = SQLTables(hStmt.handle(),
2369 nullptr, 0,
2370 nullptr, 0,
2371 nullptr, 0,
2372 joinedTableTypeString.data(), joinedTableTypeString.size());
2373 }
2374
2375 if (r != SQL_SUCCESS)
2376 qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1,
2377 hStmt.handle());
2378
2379 r = d->sqlFetchNext(hStmt);
2380 if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
2381 qSqlWarning("QODBCDriver::tables failed to retrieve table/view list"_L1,
2382 hStmt.handle());
2383 return QStringList();
2384 }
2385
2386 while (r == SQL_SUCCESS) {
2387 tl.append(qGetStringData(hStmt.handle(), 2, -1, d->unicode).toString());
2388 r = d->sqlFetchNext(hStmt);
2389 }
2390
2391 return tl;
2392}
2393
2394QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
2395{
2396 Q_D(const QODBCDriver);
2397 QSqlIndex index(tablename);
2398 if (!isOpen())
2399 return index;
2400 bool usingSpecialColumns = false;
2401 QSqlRecord rec = record(tablename);
2402
2403 SqlStmtHandle hStmt(d->hDbc);
2404 if (!hStmt.isValid()) {
2405 qSqlWarning("QODBCDriver::primaryIndex: Unable to allocate handle"_L1, d);
2406 return index;
2407 }
2408 QString catalog, schema, table;
2409 d->splitTableQualifier(tablename, catalog, schema, table);
2410
2411 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2412 SQL_ATTR_CURSOR_TYPE,
2413 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2414 SQL_IS_UINTEGER);
2415 {
2416 auto c = toSQLTCHAR(catalog);
2417 auto s = toSQLTCHAR(schema);
2418 auto t = toSQLTCHAR(table);
2419 r = SQLPrimaryKeys(hStmt.handle(),
2420 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2421 schema.isEmpty() ? nullptr : s.data(), s.size(),
2422 t.data(), t.size());
2423 }
2424
2425 // if the SQLPrimaryKeys() call does not succeed (e.g the driver
2426 // does not support it) - try an alternative method to get hold of
2427 // the primary index (e.g MS Access and FoxPro)
2428 if (r != SQL_SUCCESS) {
2429 auto c = toSQLTCHAR(catalog);
2430 auto s = toSQLTCHAR(schema);
2431 auto t = toSQLTCHAR(table);
2432 r = SQLSpecialColumns(hStmt.handle(),
2433 SQL_BEST_ROWID,
2434 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2435 schema.isEmpty() ? nullptr : s.data(), s.size(),
2436 t.data(), t.size(),
2437 SQL_SCOPE_CURROW,
2438 SQL_NULLABLE);
2439
2440 if (r != SQL_SUCCESS) {
2441 qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1,
2442 hStmt.handle());
2443 } else {
2444 usingSpecialColumns = true;
2445 }
2446 }
2447
2448 r = d->sqlFetchNext(hStmt);
2449
2450 int fakeId = 0;
2451 QString cName, idxName;
2452 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
2453 while (r == SQL_SUCCESS) {
2454 if (usingSpecialColumns) {
2455 cName = qGetStringData(hStmt.handle(), 1, -1, d->unicode).toString(); // column name
2456 idxName = QString::number(fakeId++); // invent a fake index name
2457 } else {
2458 cName = qGetStringData(hStmt.handle(), 3, -1, d->unicode).toString(); // column name
2459 idxName = qGetStringData(hStmt.handle(), 5, -1, d->unicode).toString(); // pk index name
2460 }
2461 index.append(rec.field(cName));
2462 index.setName(idxName);
2463
2464 r = d->sqlFetchNext(hStmt);
2465 }
2466 return index;
2467}
2468
2469QSqlRecord QODBCDriver::record(const QString& tablename) const
2470{
2471 Q_D(const QODBCDriver);
2472 QSqlRecord fil;
2473 if (!isOpen())
2474 return fil;
2475
2476 SqlStmtHandle hStmt(d->hDbc);
2477 if (!hStmt.isValid()) {
2478 qSqlWarning("QODBCDriver::record: Unable to allocate handle"_L1, d);
2479 return fil;
2480 }
2481
2482 QString catalog, schema, table;
2483 d->splitTableQualifier(tablename, catalog, schema, table);
2484
2485 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2486 SQL_ATTR_CURSOR_TYPE,
2487 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2488 SQL_IS_UINTEGER);
2489 {
2490 auto c = toSQLTCHAR(catalog);
2491 auto s = toSQLTCHAR(schema);
2492 auto t = toSQLTCHAR(table);
2493 r = SQLColumns(hStmt.handle(),
2494 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2495 schema.isEmpty() ? nullptr : s.data(), s.size(),
2496 t.data(), t.size(),
2497 nullptr,
2498 0);
2499 }
2500 if (r != SQL_SUCCESS)
2501 qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, hStmt.handle());
2502
2503 r = d->sqlFetchNext(hStmt);
2504 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
2505 while (r == SQL_SUCCESS) {
2506 fil.append(qMakeFieldInfo(hStmt.handle(), d));
2507 r = d->sqlFetchNext(hStmt);
2508 }
2509 return fil;
2510}
2511
2512QString QODBCDriver::formatValue(const QSqlField &field,
2513 bool trimStrings) const
2514{
2515 QString r;
2516 if (field.isNull()) {
2517 r = "NULL"_L1;
2518 } else if (field.metaType().id() == QMetaType::QDateTime) {
2519 // Use an escape sequence for the datetime fields
2520 const QDateTime dateTime = field.value().toDateTime();
2521 if (dateTime.isValid()) {
2522 const QDate dt = dateTime.date();
2523 const QTime tm = dateTime.time();
2524 // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
2525 r = "{ ts '"_L1 +
2526 QString::number(dt.year()) + u'-' +
2527 QString::number(dt.month()).rightJustified(2, u'0', true) +
2528 u'-' +
2529 QString::number(dt.day()).rightJustified(2, u'0', true) +
2530 u' ' +
2531 tm.toString() +
2532 "' }"_L1;
2533 } else
2534 r = "NULL"_L1;
2535 } else if (field.metaType().id() == QMetaType::QByteArray) {
2536 const QByteArray ba = field.value().toByteArray();
2537 r.reserve((ba.size() + 1) * 2);
2538 r = "0x"_L1;
2539 for (const char c : ba) {
2540 const uchar s = uchar(c);
2541 r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4));
2542 r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f));
2543 }
2544 } else {
2545 r = QSqlDriver::formatValue(field, trimStrings);
2546 }
2547 return r;
2548}
2549
2550QVariant QODBCDriver::handle() const
2551{
2552 Q_D(const QODBCDriver);
2553 return QVariant(QMetaType::fromType<SQLHANDLE>(), &d->hDbc);
2554}
2555
2556QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
2557{
2558 Q_D(const QODBCDriver);
2559 QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
2560 QString res = identifier;
2561 if (!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
2562 const QString quoteStr(quote);
2563 res.replace(quote, quoteStr + quoteStr);
2564 res.replace(u'.', quoteStr + u'.' + quoteStr);
2565 res = quote + res + quote;
2566 }
2567 return res;
2568}
2569
2570bool QODBCDriver::isIdentifierEscaped(const QString &identifier, IdentifierType) const
2571{
2572 Q_D(const QODBCDriver);
2573 QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
2574 return identifier.size() > 2
2575 && identifier.startsWith(quote) //left delimited
2576 && identifier.endsWith(quote); //right delimited
2577}
2578
2579QT_END_NAMESPACE
2580
2581#include "moc_qsql_odbc_p.cpp"
Definition qlist.h:81
SQLRETURN sqlFetchNext(SQLHANDLE hStmt) const
bool setConnectionOptions(const QString &connOpts)
void checkHasSQLFetchScroll()
void splitTableQualifier(const QString &qualifier, QString &catalog, QString &schema, QString &table) const
SQLRETURN sqlFetchNext(const SqlStmtHandle &hStmt) const
bool checkDriver() const
QString adjustCase(const QString &) const
void checkDateTimePrecision()
bool isStmtHandleValid() const
QVariantList fieldCache
void updateStmtHandleState()
bool prepare(const QString &query) override
Prepares the given query for execution; the query will normally use placeholders so that it can be ex...
int numRowsAffected() override
Returns the number of rows affected by the last query executed, or -1 if it cannot be determined or i...
QVariant data(int field) override
Returns the data for field index in the current row as a QVariant.
virtual ~QODBCResult()
QSqlRecord record() const override
Returns the current record if the query is active; otherwise returns an empty QSqlRecord.
bool nextResult() override
int size() override
Returns the size of the SELECT result, or -1 if it cannot be determined or if the query is not a SELE...
bool fetchFirst() override
Positions the result to the first record (row 0) in the result.
bool exec() override
Executes the query, returning true if successful; otherwise returns false.
void detachFromResultSet() override
QVariant lastInsertId() const override
Returns the object ID of the most recent inserted row if the database supports it.
void virtual_hook(int id, void *data) override
bool reset(const QString &query) override
Sets the result to use the SQL statement query for subsequent data retrieval.
bool fetchLast() override
Positions the result to the last record (last row) in the result.
bool isNull(int field) override
Returns true if the field at position index in the current row is null; otherwise returns false.
bool fetchPrevious() override
Positions the result to the previous record (row) in the result.
bool fetchNext() override
Positions the result to the next available record (row) in the result.
bool fetch(int i) override
Positions the result to an arbitrary (zero-based) row index.
QVariant handle() const override
Returns the low-level database handle for this result set wrapped in a QVariant or an invalid QVarian...
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:177
SqlStmtHandle(SQLHANDLE hDbc)
Definition qsql_odbc.cpp:51
SQLHANDLE handle() const
Definition qsql_odbc.cpp:60
bool isValid() const
Definition qsql_odbc.cpp:64
SQLHANDLE stmtHandle
Definition qsql_odbc.cpp:68
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
#define SQL_SS_TIME2
Definition qsql_odbc.cpp:37
QStringConverter::Encoding encodingForSqlTChar()
Definition qsql_odbc.cpp:90
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate *p)
static bool isAutoValue(const SQLHANDLE hStmt, int column)
static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned)
#define ODBC_CHECK_DRIVER
Definition qsql_odbc.cpp:41
static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, QStringView val)
static constexpr SQLSMALLINT TABLENAMESIZE
Definition qsql_odbc.cpp:44
static QSqlField qMakeFieldInfo(const QODBCResultPrivate *p, int i)
static QList< DiagRecord > qODBCWarn(const QODBCDriverPrivate *odbc)
static QList< DiagRecord > qODBCWarn(const QODBCResultPrivate *odbc)
static constexpr SQLSMALLINT qParamType[4]
Definition qsql_odbc.cpp:46
static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode)
static DiagRecord combineRecords(const QList< DiagRecord > &records)
static QList< DiagRecord > qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle=nullptr, const SQLHANDLE pDbC=nullptr)
static constexpr int COLNAMESIZE
Definition qsql_odbc.cpp:43
static QString fromSQLTCHAR(const C &input, qsizetype size=-1)
Definition qsql_odbc.cpp:72
static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
static QVariant qGetIntData(SQLHANDLE hStmt, int column)
static QString errorStringFromDiagRecords(const QList< DiagRecord > &records)
static QSqlError errorFromDiagRecords(const QString &err, QSqlError::ErrorType type, const QList< DiagRecord > &records)
static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType)
static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, const T *p)
static QVarLengthArray< SQLTCHAR > toSQLTCHAR(QStringView input)
static void qSqlWarning(const QString &message, T &&val)
static QVariant qGetFloatingPointData(SQLHANDLE hStmt, int column)
static QList< DiagRecord > qWarnODBCHandle(int handleType, SQLHANDLE handle)
static size_t qGetODBCVersion(const QString &connOpts)
QString description
QString errorCode
QString sqlState