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{
695 if (hasSQLFetchScroll)
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 escapeUserPassword = [](QString arg) -> QString {
1950 return u'{' + arg.replace(u'}', "}}"_L1) + u'}';
1951 };
1952 if (!user.isEmpty())
1953 connQStr += ";UID="_L1 + escapeUserPassword(user);
1954 if (!password.isEmpty())
1955 connQStr += ";PWD="_L1 + escapeUserPassword(password);
1956
1957 SQLSMALLINT cb;
1958 QVarLengthArray<SQLTCHAR, 1024> connOut(1024);
1959 {
1960 auto encoded = toSQLTCHAR(connQStr);
1961 r = SQLDriverConnect(d->hDbc,
1962 nullptr,
1963 encoded.data(), SQLSMALLINT(encoded.size()),
1964 connOut.data(), SQLSMALLINT(connOut.size()),
1965 &cb,
1966 /*SQL_DRIVER_NOPROMPT*/0);
1967 }
1968
1969 if (!SQL_SUCCEEDED(r)) {
1970 setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
1971 setOpenError(true);
1972 cleanup();
1973 return false;
1974 }
1975
1976 if (!d->checkDriver()) {
1977 setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
1978 "functionality required"), QSqlError::ConnectionError, d));
1979 setOpenError(true);
1980 cleanup();
1981 return false;
1982 }
1983
1984 d->checkUnicode();
1985 d->checkSchemaUsage();
1986 d->checkDBMS();
1987 d->checkHasSQLFetchScroll();
1988 d->checkHasMultiResults();
1989 d->checkDateTimePrecision();
1990 d->checkDefaultCase();
1991 setOpen(true);
1992 setOpenError(false);
1993 if (d->dbmsType == MSSqlServer) {
1994 QSqlQuery i(createResult());
1995 i.exec("SET QUOTED_IDENTIFIER ON"_L1);
1996 }
1997 return true;
1998}
1999
2000void QODBCDriver::close()
2001{
2002 cleanup();
2003 setOpen(false);
2004 setOpenError(false);
2005}
2006
2007void QODBCDriver::cleanup()
2008{
2009 Q_D(QODBCDriver);
2010 SQLRETURN r;
2011
2012 if (d->hDbc) {
2013 // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
2014 if (isOpen()) {
2015 r = SQLDisconnect(d->hDbc);
2016 if (r != SQL_SUCCESS)
2017 qSqlWarning("QODBCDriver::disconnect: Unable to disconnect datasource"_L1, d);
2018 else
2019 d->disconnectCount++;
2020 }
2021
2022 r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
2023 if (r != SQL_SUCCESS)
2024 qSqlWarning("QODBCDriver::cleanup: Unable to free connection handle"_L1, d);
2025 d->hDbc = 0;
2026 }
2027
2028 if (d->hEnv) {
2029 r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
2030 if (r != SQL_SUCCESS)
2031 qSqlWarning("QODBCDriver::cleanup: Unable to free environment handle"_L1, d);
2032 d->hEnv = 0;
2033 }
2034}
2035
2036// checks whether the server can return char, varchar and longvarchar
2037// as two byte unicode characters
2039{
2040 SQLRETURN r;
2041 SQLUINTEGER fFunc;
2042
2043 unicode = false;
2044 r = SQLGetInfo(hDbc,
2045 SQL_CONVERT_CHAR,
2046 (SQLPOINTER)&fFunc,
2047 sizeof(fFunc),
2048 NULL);
2049 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WCHAR)) {
2050 unicode = true;
2051 return;
2052 }
2053
2054 r = SQLGetInfo(hDbc,
2055 SQL_CONVERT_VARCHAR,
2056 (SQLPOINTER)&fFunc,
2057 sizeof(fFunc),
2058 NULL);
2059 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WVARCHAR)) {
2060 unicode = true;
2061 return;
2062 }
2063
2064 r = SQLGetInfo(hDbc,
2065 SQL_CONVERT_LONGVARCHAR,
2066 (SQLPOINTER)&fFunc,
2067 sizeof(fFunc),
2068 NULL);
2069 if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
2070 unicode = true;
2071 return;
2072 }
2073
2074 SqlStmtHandle hStmt(hDbc);
2075 // for databases which do not return something useful in SQLGetInfo and are picky about a
2076 // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle
2077 const std::array<QStringView, 3> statements = {
2078 u"select 'test'",
2079 u"values('test')",
2080 u"select 'test' from dual",
2081 };
2082 for (const auto &statement : statements) {
2083 auto encoded = toSQLTCHAR(statement);
2084 r = SQLExecDirect(hStmt.handle(), encoded.data(), SQLINTEGER(encoded.size()));
2085 if (r == SQL_SUCCESS)
2086 break;
2087 }
2088 if (r == SQL_SUCCESS) {
2089 r = SQLFetch(hStmt.handle());
2090 if (r == SQL_SUCCESS) {
2091 QVarLengthArray<SQLWCHAR, 10> buffer(10);
2092 r = SQLGetData(hStmt.handle(), 1, SQL_C_WCHAR, buffer.data(),
2093 buffer.size() * sizeof(SQLWCHAR), NULL);
2094 if (r == SQL_SUCCESS && fromSQLTCHAR(buffer) == "test"_L1) {
2095 unicode = true;
2096 }
2097 }
2098 }
2099}
2100
2102{
2103#ifdef ODBC_CHECK_DRIVER
2104 static constexpr SQLUSMALLINT reqFunc[] = {
2105 SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
2106 SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
2107 SQL_API_SQLGETINFO, SQL_API_SQLTABLES
2108 };
2109
2110 // these functions are optional
2111 static constexpr SQLUSMALLINT optFunc[] = {
2112 SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT
2113 };
2114
2115 SQLRETURN r;
2116 SQLUSMALLINT sup;
2117
2118 // check the required functions
2119 for (const SQLUSMALLINT func : reqFunc) {
2120
2121 r = SQLGetFunctions(hDbc, func, &sup);
2122
2123 if (r != SQL_SUCCESS) {
2124 qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
2125 return false;
2126 }
2127 if (sup == SQL_FALSE) {
2128 qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support all needed "
2129 "functionality (func id %1).\nPlease look at the Qt SQL Module "
2130 "Driver documentation for more information."_L1)
2131 .arg(QString::number(func)), this);
2132 return false;
2133 }
2134 }
2135
2136 // these functions are optional and just generate a warning
2137 for (const SQLUSMALLINT func : optFunc) {
2138
2139 r = SQLGetFunctions(hDbc, func, &sup);
2140
2141 if (r != SQL_SUCCESS) {
2142 qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
2143 return false;
2144 }
2145 if (sup == SQL_FALSE) {
2146 qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support some "
2147 "non-critical functions (func id %1)."_L1)
2148 .arg(QString::number(func)), this);
2149 return true;
2150 }
2151 }
2152#endif //ODBC_CHECK_DRIVER
2153
2154 return true;
2155}
2156
2158{
2159 SQLRETURN r;
2160 SQLUINTEGER val;
2161
2162 r = SQLGetInfo(hDbc,
2163 SQL_SCHEMA_USAGE,
2164 (SQLPOINTER) &val,
2165 sizeof(val),
2166 NULL);
2167 if (SQL_SUCCEEDED(r))
2168 useSchema = (val != 0);
2169}
2170
2172{
2173 SQLRETURN r;
2174 QVarLengthArray<SQLTCHAR, 200> serverString(200);
2175 SQLSMALLINT t;
2176
2177 r = SQLGetInfo(hDbc,
2178 SQL_DBMS_NAME,
2179 serverString.data(),
2180 SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
2181 &t);
2182 if (SQL_SUCCEEDED(r)) {
2183 const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
2184 if (serverType.contains("PostgreSQL"_L1, Qt::CaseInsensitive))
2185 dbmsType = QSqlDriver::PostgreSQL;
2186 else if (serverType.contains("Oracle"_L1, Qt::CaseInsensitive))
2187 dbmsType = QSqlDriver::Oracle;
2188 else if (serverType.contains("MySql"_L1, Qt::CaseInsensitive))
2189 dbmsType = QSqlDriver::MySqlServer;
2190 else if (serverType.contains("Microsoft SQL Server"_L1, Qt::CaseInsensitive))
2191 dbmsType = QSqlDriver::MSSqlServer;
2192 else if (serverType.contains("Sybase"_L1, Qt::CaseInsensitive))
2193 dbmsType = QSqlDriver::Sybase;
2194 }
2195 r = SQLGetInfo(hDbc,
2196 SQL_DRIVER_NAME,
2197 serverString.data(),
2198 SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
2199 &t);
2200 if (SQL_SUCCEEDED(r)) {
2201 const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
2202 isFreeTDSDriver = serverType.contains("tdsodbc"_L1, Qt::CaseInsensitive);
2204 }
2205}
2206
2208{
2209 SQLUSMALLINT sup;
2210 SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
2211 if ((!SQL_SUCCEEDED(r)) || sup != SQL_TRUE) {
2212 hasSQLFetchScroll = false;
2213 qSqlWarning("QODBCDriver::checkHasSQLFetchScroll: Driver doesn't support "
2214 "scrollable result sets, use forward only mode for queries"_L1, this);
2215 }
2216}
2217
2219{
2220 QVarLengthArray<SQLTCHAR, 2> driverResponse(2);
2221 SQLSMALLINT length;
2222 SQLRETURN r = SQLGetInfo(hDbc,
2223 SQL_MULT_RESULT_SETS,
2224 driverResponse.data(),
2225 SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
2226 &length);
2227 if (SQL_SUCCEEDED(r))
2228 hasMultiResultSets = fromSQLTCHAR(driverResponse, length / sizeof(SQLTCHAR)).startsWith(u'Y');
2229}
2230
2232{
2233 SQLINTEGER columnSize;
2234 SqlStmtHandle hStmt(hDbc);
2235
2236 if (!hStmt.isValid())
2237 return;
2238
2239 SQLRETURN r = SQLGetTypeInfo(hStmt.handle(), SQL_TIMESTAMP);
2240 if (SQL_SUCCEEDED(r)) {
2241 r = SQLFetch(hStmt.handle());
2242 if (SQL_SUCCEEDED(r)) {
2243 if (SQLGetData(hStmt.handle(), 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS)
2244 datetimePrecision = (int)columnSize;
2245 }
2246 }
2247}
2248
2249QSqlResult *QODBCDriver::createResult() const
2250{
2251 return new QODBCResult(this);
2252}
2253
2254bool QODBCDriver::beginTransaction()
2255{
2256 Q_D(QODBCDriver);
2257 if (!isOpen()) {
2258 qSqlWarning("QODBCDriver::beginTransaction: Database not open"_L1, d);
2259 return false;
2260 }
2261 SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
2262 SQLRETURN r = SQLSetConnectAttr(d->hDbc,
2263 SQL_ATTR_AUTOCOMMIT,
2264 (SQLPOINTER)size_t(ac),
2265 sizeof(ac));
2266 if (r != SQL_SUCCESS) {
2267 setLastError(qMakeError(tr("Unable to disable autocommit"),
2268 QSqlError::TransactionError, d));
2269 return false;
2270 }
2271 return true;
2272}
2273
2274bool QODBCDriver::commitTransaction()
2275{
2276 Q_D(QODBCDriver);
2277 if (!isOpen()) {
2278 qSqlWarning("QODBCDriver::commitTransaction: Database not open"_L1, d);
2279 return false;
2280 }
2281 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
2282 d->hDbc,
2283 SQL_COMMIT);
2284 if (r != SQL_SUCCESS) {
2285 setLastError(qMakeError(tr("Unable to commit transaction"),
2286 QSqlError::TransactionError, d));
2287 return false;
2288 }
2289 return endTrans();
2290}
2291
2292bool QODBCDriver::rollbackTransaction()
2293{
2294 Q_D(QODBCDriver);
2295 if (!isOpen()) {
2296 qSqlWarning("QODBCDriver::rollbackTransaction: Database not open"_L1, d);
2297 return false;
2298 }
2299 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
2300 d->hDbc,
2301 SQL_ROLLBACK);
2302 if (r != SQL_SUCCESS) {
2303 setLastError(qMakeError(tr("Unable to rollback transaction"),
2304 QSqlError::TransactionError, d));
2305 return false;
2306 }
2307 return endTrans();
2308}
2309
2310bool QODBCDriver::endTrans()
2311{
2312 Q_D(QODBCDriver);
2313 SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
2314 SQLRETURN r = SQLSetConnectAttr(d->hDbc,
2315 SQL_ATTR_AUTOCOMMIT,
2316 (SQLPOINTER)size_t(ac),
2317 sizeof(ac));
2318 if (r != SQL_SUCCESS) {
2319 setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d));
2320 return false;
2321 }
2322 return true;
2323}
2324
2325QStringList QODBCDriver::tables(QSql::TableType type) const
2326{
2327 Q_D(const QODBCDriver);
2328 QStringList tl;
2329 if (!isOpen())
2330 return tl;
2331
2332 SqlStmtHandle hStmt(d->hDbc);
2333 if (!hStmt.isValid()) {
2334 qSqlWarning("QODBCDriver::tables: Unable to allocate handle"_L1, d);
2335 return tl;
2336 }
2337 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2338 SQL_ATTR_CURSOR_TYPE,
2339 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2340 SQL_IS_UINTEGER);
2341 QStringList tableType;
2342 if (type & QSql::Tables)
2343 tableType += "TABLE"_L1;
2344 if (type & QSql::Views)
2345 tableType += "VIEW"_L1;
2346 if (type & QSql::SystemTables)
2347 tableType += "SYSTEM TABLE"_L1;
2348 if (tableType.isEmpty())
2349 return tl;
2350
2351 {
2352 auto joinedTableTypeString = toSQLTCHAR(tableType.join(u','));
2353
2354 r = SQLTables(hStmt.handle(),
2355 nullptr, 0,
2356 nullptr, 0,
2357 nullptr, 0,
2358 joinedTableTypeString.data(), joinedTableTypeString.size());
2359 }
2360
2361 if (r != SQL_SUCCESS)
2362 qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1,
2363 hStmt.handle());
2364
2365 r = d->sqlFetchNext(hStmt);
2366 if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
2367 qSqlWarning("QODBCDriver::tables failed to retrieve table/view list"_L1,
2368 hStmt.handle());
2369 return QStringList();
2370 }
2371
2372 while (r == SQL_SUCCESS) {
2373 tl.append(qGetStringData(hStmt.handle(), 2, -1, d->unicode).toString());
2374 r = d->sqlFetchNext(hStmt);
2375 }
2376
2377 return tl;
2378}
2379
2380QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
2381{
2382 Q_D(const QODBCDriver);
2383 QSqlIndex index(tablename);
2384 if (!isOpen())
2385 return index;
2386 bool usingSpecialColumns = false;
2387 QSqlRecord rec = record(tablename);
2388
2389 SqlStmtHandle hStmt(d->hDbc);
2390 if (!hStmt.isValid()) {
2391 qSqlWarning("QODBCDriver::primaryIndex: Unable to allocate handle"_L1, d);
2392 return index;
2393 }
2394 QString catalog, schema, table;
2395 d->splitTableQualifier(tablename, catalog, schema, table);
2396
2397 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2398 SQL_ATTR_CURSOR_TYPE,
2399 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2400 SQL_IS_UINTEGER);
2401 {
2402 auto c = toSQLTCHAR(catalog);
2403 auto s = toSQLTCHAR(schema);
2404 auto t = toSQLTCHAR(table);
2405 r = SQLPrimaryKeys(hStmt.handle(),
2406 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2407 schema.isEmpty() ? nullptr : s.data(), s.size(),
2408 t.data(), t.size());
2409 }
2410
2411 // if the SQLPrimaryKeys() call does not succeed (e.g the driver
2412 // does not support it) - try an alternative method to get hold of
2413 // the primary index (e.g MS Access and FoxPro)
2414 if (r != SQL_SUCCESS) {
2415 auto c = toSQLTCHAR(catalog);
2416 auto s = toSQLTCHAR(schema);
2417 auto t = toSQLTCHAR(table);
2418 r = SQLSpecialColumns(hStmt.handle(),
2419 SQL_BEST_ROWID,
2420 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2421 schema.isEmpty() ? nullptr : s.data(), s.size(),
2422 t.data(), t.size(),
2423 SQL_SCOPE_CURROW,
2424 SQL_NULLABLE);
2425
2426 if (r != SQL_SUCCESS) {
2427 qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1,
2428 hStmt.handle());
2429 } else {
2430 usingSpecialColumns = true;
2431 }
2432 }
2433
2434 r = d->sqlFetchNext(hStmt);
2435
2436 int fakeId = 0;
2437 QString cName, idxName;
2438 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
2439 while (r == SQL_SUCCESS) {
2440 if (usingSpecialColumns) {
2441 cName = qGetStringData(hStmt.handle(), 1, -1, d->unicode).toString(); // column name
2442 idxName = QString::number(fakeId++); // invent a fake index name
2443 } else {
2444 cName = qGetStringData(hStmt.handle(), 3, -1, d->unicode).toString(); // column name
2445 idxName = qGetStringData(hStmt.handle(), 5, -1, d->unicode).toString(); // pk index name
2446 }
2447 index.append(rec.field(cName));
2448 index.setName(idxName);
2449
2450 r = d->sqlFetchNext(hStmt);
2451 }
2452 return index;
2453}
2454
2455QSqlRecord QODBCDriver::record(const QString& tablename) const
2456{
2457 Q_D(const QODBCDriver);
2458 QSqlRecord fil;
2459 if (!isOpen())
2460 return fil;
2461
2462 SqlStmtHandle hStmt(d->hDbc);
2463 if (!hStmt.isValid()) {
2464 qSqlWarning("QODBCDriver::record: Unable to allocate handle"_L1, d);
2465 return fil;
2466 }
2467
2468 QString catalog, schema, table;
2469 d->splitTableQualifier(tablename, catalog, schema, table);
2470
2471 SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
2472 SQL_ATTR_CURSOR_TYPE,
2473 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
2474 SQL_IS_UINTEGER);
2475 {
2476 auto c = toSQLTCHAR(catalog);
2477 auto s = toSQLTCHAR(schema);
2478 auto t = toSQLTCHAR(table);
2479 r = SQLColumns(hStmt.handle(),
2480 catalog.isEmpty() ? nullptr : c.data(), c.size(),
2481 schema.isEmpty() ? nullptr : s.data(), s.size(),
2482 t.data(), t.size(),
2483 nullptr,
2484 0);
2485 }
2486 if (r != SQL_SUCCESS)
2487 qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, hStmt.handle());
2488
2489 r = d->sqlFetchNext(hStmt);
2490 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
2491 while (r == SQL_SUCCESS) {
2492 fil.append(qMakeFieldInfo(hStmt.handle(), d));
2493 r = d->sqlFetchNext(hStmt);
2494 }
2495 return fil;
2496}
2497
2498QString QODBCDriver::formatValue(const QSqlField &field,
2499 bool trimStrings) const
2500{
2501 QString r;
2502 if (field.isNull()) {
2503 r = "NULL"_L1;
2504 } else if (field.metaType().id() == QMetaType::QDateTime) {
2505 // Use an escape sequence for the datetime fields
2506 const QDateTime dateTime = field.value().toDateTime();
2507 if (dateTime.isValid()) {
2508 const QDate dt = dateTime.date();
2509 const QTime tm = dateTime.time();
2510 // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
2511 r = "{ ts '"_L1 +
2512 QString::number(dt.year()) + u'-' +
2513 QString::number(dt.month()).rightJustified(2, u'0', true) +
2514 u'-' +
2515 QString::number(dt.day()).rightJustified(2, u'0', true) +
2516 u' ' +
2517 tm.toString() +
2518 "' }"_L1;
2519 } else
2520 r = "NULL"_L1;
2521 } else if (field.metaType().id() == QMetaType::QByteArray) {
2522 const QByteArray ba = field.value().toByteArray();
2523 r.reserve((ba.size() + 1) * 2);
2524 r = "0x"_L1;
2525 for (const char c : ba) {
2526 const uchar s = uchar(c);
2527 r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4));
2528 r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f));
2529 }
2530 } else {
2531 r = QSqlDriver::formatValue(field, trimStrings);
2532 }
2533 return r;
2534}
2535
2536QVariant QODBCDriver::handle() const
2537{
2538 Q_D(const QODBCDriver);
2539 return QVariant(QMetaType::fromType<SQLHANDLE>(), &d->hDbc);
2540}
2541
2542QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
2543{
2544 Q_D(const QODBCDriver);
2545 QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
2546 QString res = identifier;
2547 if (!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
2548 const QString quoteStr(quote);
2549 res.replace(quote, quoteStr + quoteStr);
2550 res.replace(u'.', quoteStr + u'.' + quoteStr);
2551 res = quote + res + quote;
2552 }
2553 return res;
2554}
2555
2556bool QODBCDriver::isIdentifierEscaped(const QString &identifier, IdentifierType) const
2557{
2558 Q_D(const QODBCDriver);
2559 QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
2560 return identifier.size() > 2
2561 && identifier.startsWith(quote) //left delimited
2562 && identifier.endsWith(quote); //right delimited
2563}
2564
2565QT_END_NAMESPACE
2566
2567#include "moc_qsql_odbc_p.cpp"
Definition qlist.h:80
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...
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
#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