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