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

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