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

source code of qtbase/src/plugins/sqldrivers/odbc/qsql_odbc.cpp