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

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