diff options
Diffstat (limited to 'src/plugins/sqldrivers/odbc/qsql_odbc.cpp')
-rw-r--r-- | src/plugins/sqldrivers/odbc/qsql_odbc.cpp | 1539 |
1 files changed, 742 insertions, 797 deletions
diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index a58c782b60..77137f3b3c 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtSql module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsql_odbc_p.h" #include <qsqlrecord.h> @@ -46,10 +10,12 @@ #include <qcoreapplication.h> #include <qdatetime.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qmath.h> #include <qsqlerror.h> #include <qsqlfield.h> #include <qsqlindex.h> +#include <qstringconverter.h> #include <qstringlist.h> #include <qvariant.h> #include <qvarlengtharray.h> @@ -57,59 +23,89 @@ #include <QSqlQuery> #include <QtSql/private/qsqldriver_p.h> #include <QtSql/private/qsqlresult_p.h> +#include "private/qtools_p.h" QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc") + +using namespace Qt::StringLiterals; + +// non-standard ODBC SQL data type from SQL Server sometimes used instead of SQL_TIME +#ifndef SQL_SS_TIME2 +#define SQL_SS_TIME2 (-154) +#endif + // undefine this to prevent initial check of the ODBC driver #define ODBC_CHECK_DRIVER -static const int COLNAMESIZE = 256; -static const SQLSMALLINT TABLENAMESIZE = 128; +static constexpr int COLNAMESIZE = 256; +static constexpr SQLSMALLINT TABLENAMESIZE = 128; //Map Qt parameter types to ODBC types -static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT }; +static constexpr SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT }; -inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, qsizetype size=-1) +class SqlStmtHandle { - QString result; +public: + SqlStmtHandle(SQLHANDLE hDbc) + { + SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &stmtHandle); + } + ~SqlStmtHandle() + { + if (stmtHandle != SQL_NULL_HSTMT) + SQLFreeHandle(SQL_HANDLE_STMT, stmtHandle); + } + SQLHANDLE handle() const + { + return stmtHandle; + } + bool isValid() const + { + return stmtHandle != SQL_NULL_HSTMT; + } + SQLHANDLE stmtHandle = SQL_NULL_HSTMT; +}; +template<typename C, int SIZE = sizeof(SQLTCHAR)> +inline static QString fromSQLTCHAR(const C &input, qsizetype size = -1) +{ // Remove any trailing \0 as some drivers misguidedly append one - int realsize = qMin(size, input.size()); - if(realsize > 0 && input[realsize-1] == 0) + qsizetype realsize = qMin(size, input.size()); + if (realsize > 0 && input[realsize - 1] == 0) realsize--; - switch(sizeof(SQLTCHAR)) { - case 1: - result=QString::fromUtf8((const char *)input.constData(), realsize); - break; - case 2: - result = QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize); - break; - case 4: - result = QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize); - break; - default: - qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR))); - } - return result; + if constexpr (SIZE == 1) + return QString::fromUtf8(reinterpret_cast<const char *>(input.constData()), realsize); + else if constexpr (SIZE == 2) + return QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize); + else if constexpr (SIZE == 4) + return QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize); + else + static_assert(QtPrivate::value_dependent_false<SIZE>(), + "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4"); } -inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input) +template<int SIZE = sizeof(SQLTCHAR)> +QStringConverter::Encoding encodingForSqlTChar() +{ + if constexpr (SIZE == 1) + return QStringConverter::Utf8; + else if constexpr (SIZE == 2) + return QStringConverter::Utf16; + else if constexpr (SIZE == 4) + return QStringConverter::Utf32; + else + static_assert(QtPrivate::value_dependent_false<SIZE>(), + "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4"); +} + +inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(QStringView input) { QVarLengthArray<SQLTCHAR> result; - result.resize(input.size()); - switch(sizeof(SQLTCHAR)) { - case 1: - memcpy(result.data(), input.toUtf8().data(), input.size()); - break; - case 2: - memcpy(result.data(), input.unicode(), input.size() * 2); - break; - case 4: - memcpy(result.data(), input.toUcs4().data(), input.size() * 4); - break; - default: - qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR))); - } - result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't. + QStringEncoder enc(encodingForSqlTChar()); + result.resize(enc.requiredSpace(input.size())); + const auto end = enc.appendToBuffer(reinterpret_cast<char *>(result.data()), input); + result.resize((end - reinterpret_cast<char *>(result.data())) / sizeof(SQLTCHAR)); return result; } @@ -118,7 +114,7 @@ class QODBCDriverPrivate : public QSqlDriverPrivate Q_DECLARE_PUBLIC(QODBCDriver) public: - enum DefaultCase {Lower, Mixed, Upper, Sensitive}; + enum class DefaultCase {Lower, Mixed, Upper, Sensitive}; using QSqlDriverPrivate::QSqlDriverPrivate; SQLHANDLE hEnv = nullptr; @@ -139,15 +135,18 @@ public: void checkHasMultiResults(); void checkSchemaUsage(); void checkDateTimePrecision(); + void checkDefaultCase(); bool setConnectionOptions(const QString& connOpts); void splitTableQualifier(const QString &qualifier, QString &catalog, - QString &schema, QString &table); - DefaultCase defaultCase() const; + QString &schema, QString &table) const; QString adjustCase(const QString&) const; QChar quoteChar(); + SQLRETURN sqlFetchNext(const SqlStmtHandle &hStmt) const; + SQLRETURN sqlFetchNext(SQLHANDLE hStmt) const; private: bool isQuoteInitialized = false; - QChar quote = QLatin1Char('"'); + QChar quote = u'"'; + DefaultCase m_defaultCase = DefaultCase::Mixed; }; class QODBCResultPrivate; @@ -227,118 +226,121 @@ void QODBCResultPrivate::updateStmtHandleState() disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0; } -static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0) +struct DiagRecord +{ + QString description; + QString sqlState; + QString errorCode; +}; +static QList<DiagRecord> qWarnODBCHandle(int handleType, SQLHANDLE handle) { - SQLINTEGER nativeCode_ = 0; + SQLINTEGER nativeCode = 0; SQLSMALLINT msgLen = 0; + SQLSMALLINT i = 1; SQLRETURN r = SQL_NO_DATA; - SQLTCHAR state_[SQL_SQLSTATE_SIZE+1]; - QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH); - QString result; - int i = 1; + QVarLengthArray<SQLTCHAR, SQL_SQLSTATE_SIZE + 1> state(SQL_SQLSTATE_SIZE + 1); + QVarLengthArray<SQLTCHAR, SQL_MAX_MESSAGE_LENGTH + 1> description(SQL_MAX_MESSAGE_LENGTH + 1); + QList<DiagRecord> result; - description_[0] = 0; + if (!handle) + return result; do { r = SQLGetDiagRec(handleType, handle, i, - state_, - &nativeCode_, - 0, - 0, + state.data(), + &nativeCode, + description.data(), + description.size(), &msgLen); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0) - description_.resize(msgLen+1); - r = SQLGetDiagRec(handleType, - handle, - i, - state_, - &nativeCode_, - description_.data(), - description_.size(), - &msgLen); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - if (nativeCode) - *nativeCode = nativeCode_; - const QString tmpstore = fromSQLTCHAR(description_, msgLen); - if(result != tmpstore) { - if(!result.isEmpty()) - result += QLatin1Char(' '); - result += tmpstore; - } + if (msgLen >= description.size()) { + description.resize(msgLen + 1); // incl. \0 termination + continue; + } + if (SQL_SUCCEEDED(r)) { + result.push_back({fromSQLTCHAR(description, msgLen), + fromSQLTCHAR(state), + QString::number(nativeCode)}); } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) { - return result; + break; } ++i; } while (r != SQL_NO_DATA); return result; } -static QString qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle = 0, - const SQLHANDLE pDbC = 0, int *nativeCode = 0) +static QList<DiagRecord> qODBCWarn(const SQLHANDLE hStmt, + const SQLHANDLE envHandle = nullptr, + const SQLHANDLE pDbC = nullptr) { - QString result; - if (envHandle) - result += qWarnODBCHandle(SQL_HANDLE_ENV, envHandle, nativeCode); - if (pDbC) { - const QString dMessage = qWarnODBCHandle(SQL_HANDLE_DBC, pDbC, nativeCode); - if (!dMessage.isEmpty()) { - if (!result.isEmpty()) - result += QLatin1Char(' '); - result += dMessage; - } - } - if (hStmt) { - const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode); - if (!hMessage.isEmpty()) { - if (!result.isEmpty()) - result += QLatin1Char(' '); - result += hMessage; - } - } + QList<DiagRecord> result; + result.append(qWarnODBCHandle(SQL_HANDLE_ENV, envHandle)); + result.append(qWarnODBCHandle(SQL_HANDLE_DBC, pDbC)); + result.append(qWarnODBCHandle(SQL_HANDLE_STMT, hStmt)); return result; } -static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = 0) +static QList<DiagRecord> qODBCWarn(const QODBCResultPrivate *odbc) +{ + return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc()); +} + +static QList<DiagRecord> qODBCWarn(const QODBCDriverPrivate *odbc) { - return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode); + return qODBCWarn(nullptr, odbc->hEnv, odbc->hDbc); } -static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0) +static DiagRecord combineRecords(const QList<DiagRecord> &records) { - return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode); + const auto add = [](const DiagRecord &a, const DiagRecord &b) { + return DiagRecord{a.description + u' ' + b.description, + a.sqlState + u';' + b.sqlState, + a.errorCode + u';' + b.errorCode}; + }; + if (records.isEmpty()) + return {}; + return std::accumulate(std::next(records.begin()), records.end(), records.front(), add); } -static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc) +static QSqlError errorFromDiagRecords(const QString &err, + QSqlError::ErrorType type, + const QList<DiagRecord> &records) { - qWarning() << message << "\tError:" << qODBCWarn(odbc); + if (records.empty()) + return QSqlError("QODBC: unknown error"_L1, {}, type, {}); + const auto combined = combineRecords(records); + return QSqlError("QODBC: "_L1 + err, combined.description + ", "_L1 + combined.sqlState, type, + combined.errorCode); } -static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc) +static QString errorStringFromDiagRecords(const QList<DiagRecord>& records) { - qWarning() << message << "\tError:" << qODBCWarn(odbc); + const auto combined = combineRecords(records); + return combined.description; } -static void qSqlWarning(const QString &message, const SQLHANDLE hStmt) +template<class T> +static void qSqlWarning(const QString &message, T &&val) { - qWarning() << message << "\tError:" << qODBCWarn(hStmt); + const auto addMsg = errorStringFromDiagRecords(qODBCWarn(val)); + if (addMsg.isEmpty()) + qCWarning(lcOdbc) << message; + else + qCWarning(lcOdbc) << message << "\tError:" << addMsg; } -static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* p) +static QSqlError qMakeError(const QString &err, + QSqlError::ErrorType type, + const QODBCResultPrivate *p) { - int nativeCode = -1; - QString message = qODBCWarn(p, &nativeCode); - return QSqlError(QLatin1String("QODBC: ") + err, message, type, - nativeCode != -1 ? QString::number(nativeCode) : QString()); + return errorFromDiagRecords(err, type, qODBCWarn(p)); } -static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, - const QODBCDriverPrivate* p) +static QSqlError qMakeError(const QString &err, + QSqlError::ErrorType type, + const QODBCDriverPrivate *p) { - int nativeCode = -1; - QString message = qODBCWarn(p, &nativeCode); - return QSqlError(QLatin1String("QODBC: ") + err, message, type, - nativeCode != -1 ? QString::number(nativeCode) : QString()); + return errorFromDiagRecords(err, type, qODBCWarn(p)); } static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) @@ -347,12 +349,16 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) switch (sqltype) { case SQL_DECIMAL: case SQL_NUMERIC: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: + case SQL_FLOAT: // 24 or 53 bits precision + case SQL_DOUBLE:// 53 bits type = QMetaType::Double; break; + case SQL_REAL: // 24 bits + type = QMetaType::Float; + break; case SQL_SMALLINT: + type = isSigned ? QMetaType::Short : QMetaType::UShort; + break; case SQL_INTEGER: case SQL_BIT: type = isSigned ? QMetaType::Int : QMetaType::UInt; @@ -372,6 +378,7 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) case SQL_TYPE_DATE: type = QMetaType::QDate; break; + case SQL_SS_TIME2: case SQL_TIME: case SQL_TYPE_TIME: type = QMetaType::QTime; @@ -400,119 +407,69 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) return QMetaType(type); } -static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false) +template <typename CT> +static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType) { QString fieldVal; SQLRETURN r = SQL_ERROR; SQLLEN lengthIndicator = 0; - - // NB! colSize must be a multiple of 2 for unicode enabled DBs - if (colSize <= 0) { - colSize = 256; - } else if (colSize > 65536) { // limit buffer size to 64 KB - colSize = 65536; - } else { - colSize++; // make sure there is room for more than the 0 termination - } - if(unicode) { + QVarLengthArray<CT> buf(colSize); + while (true) { r = SQLGetData(hStmt, - column+1, - SQL_C_TCHAR, - NULL, - 0, - &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) - colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1); - QVarLengthArray<SQLTCHAR> buf(colSize); - memset(buf.data(), 0, colSize*sizeof(SQLTCHAR)); - while (true) { - r = SQLGetData(hStmt, - column+1, - SQL_C_TCHAR, - (SQLPOINTER)buf.data(), - colSize*sizeof(SQLTCHAR), - &lengthIndicator); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - if (lengthIndicator == SQL_NULL_DATA) { - fieldVal.clear(); - break; - } - // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned - // instead of the length (which sometimes was wrong in older versions) - // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx - // if length indicator equals SQL_NO_TOTAL, indicating that - // more data can be fetched, but size not known, collect data - // and fetch next block - if (lengthIndicator == SQL_NO_TOTAL) { - fieldVal += fromSQLTCHAR(buf, colSize); - continue; - } - // if SQL_SUCCESS_WITH_INFO is returned, indicating that - // more data can be fetched, the length indicator does NOT - // contain the number of bytes returned - it contains the - // total number of bytes that CAN be fetched - int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR)); - fieldVal += fromSQLTCHAR(buf, rSize); - if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) { - // workaround for Drivermanagers that don't return SQL_NO_DATA - break; - } - } else if (r == SQL_NO_DATA) { - break; - } else { - qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; - fieldVal.clear(); - break; + column + 1, + targetType, + SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)), + &lengthIndicator); + if (SQL_SUCCEEDED(r)) { + if (lengthIndicator == SQL_NULL_DATA) { + return {}; } - } - } else { - r = SQLGetData(hStmt, - column+1, - SQL_C_CHAR, - NULL, - 0, - &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) - colSize = lengthIndicator + 1; - QVarLengthArray<SQLCHAR> buf(colSize); - while (true) { - r = SQLGetData(hStmt, - column+1, - SQL_C_CHAR, - (SQLPOINTER)buf.data(), - colSize, - &lengthIndicator); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { - fieldVal.clear(); - break; - } - // if SQL_SUCCESS_WITH_INFO is returned, indicating that - // more data can be fetched, the length indicator does NOT - // contain the number of bytes returned - it contains the - // total number of bytes that CAN be fetched - qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator; - // Remove any trailing \0 as some drivers misguidedly append one - int realsize = qMin(rSize, buf.size()); - if (realsize > 0 && buf[realsize - 1] == 0) - realsize--; - fieldVal += QString::fromUtf8(reinterpret_cast<const char *>(buf.constData()), realsize); - if (lengthIndicator < SQLLEN(colSize)) { - // workaround for Drivermanagers that don't return SQL_NO_DATA - break; - } - } else if (r == SQL_NO_DATA) { - break; - } else { - qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; - fieldVal.clear(); + // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned + // instead of the length (which sometimes was wrong in older versions) + // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx + // if length indicator equals SQL_NO_TOTAL, indicating that + // more data can be fetched, but size not known, collect data + // and fetch next block + if (lengthIndicator == SQL_NO_TOTAL) { + fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, buf.size()); + continue; + } + // if SQL_SUCCESS_WITH_INFO is returned, indicating that + // more data can be fetched, the length indicator does NOT + // contain the number of bytes returned - it contains the + // total number of bytes that CAN be fetched + const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) + ? buf.size() + : qsizetype(lengthIndicator / sizeof(CT)); + fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, rSize); + // lengthIndicator does not contain the termination character + if (lengthIndicator < SQLLEN((buf.size() - 1) * sizeof(CT))) { + // workaround for Drivermanagers that don't return SQL_NO_DATA break; } + } else if (r == SQL_NO_DATA) { + break; + } else { + qSqlWarning("QODBC::getStringData: Error while fetching data"_L1, hStmt); + return {}; } } return fieldVal; } +static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode) +{ + if (colSize <= 0) { + colSize = 256; // default Prealloc size of QVarLengthArray + } else if (colSize > 65536) { // limit buffer size to 64 KB + colSize = 65536; + } else { + colSize++; // make sure there is room for more than the 0 termination + } + return unicode ? getStringDataImpl<SQLTCHAR>(hStmt, column, colSize, SQL_C_TCHAR) + : getStringDataImpl<SQLCHAR>(hStmt, column, colSize, SQL_C_CHAR); +} + static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) { QByteArray fieldVal; @@ -524,19 +481,19 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) SQLLEN lengthIndicator = 0; SQLRETURN r = SQL_ERROR; - QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); + QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE); r = SQLDescribeCol(hStmt, column + 1, - colName.data(), - COLNAMESIZE, + colName.data(), SQLSMALLINT(colName.size()), &colNameLen, &colType, &colSize, &colScale, &nullable); if (r != SQL_SUCCESS) - qWarning() << "qGetBinaryData: Unable to describe column" << column; + qSqlWarning(("QODBC::qGetBinaryData: Unable to describe column %1"_L1) + .arg(QString::number(column)), hStmt); // SQLDescribeCol may return 0 if size cannot be determined if (!colSize) colSize = 255; @@ -551,10 +508,10 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) const_cast<char *>(fieldVal.constData() + read), colSize, &lengthIndicator); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + if (!SQL_SUCCEEDED(r)) break; if (lengthIndicator == SQL_NULL_DATA) - return QVariant(QMetaType(QVariant::ByteArray)); + return QVariant(QMetaType(QMetaType::QByteArray)); if (lengthIndicator > SQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) { read += colSize; colSize = 65536; @@ -580,7 +537,7 @@ static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true) (SQLPOINTER)&intbuf, sizeof(intbuf), &lengthIndicator); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + if (!SQL_SUCCEEDED(r)) return QVariant(); if (lengthIndicator == SQL_NULL_DATA) return QVariant(QMetaType::fromType<int>()); @@ -600,10 +557,10 @@ static QVariant qGetDoubleData(SQLHANDLE hStmt, int column) (SQLPOINTER) &dblbuf, 0, &lengthIndicator); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (!SQL_SUCCEEDED(r)) { return QVariant(); } - if(lengthIndicator == SQL_NULL_DATA) + if (lengthIndicator == SQL_NULL_DATA) return QVariant(QMetaType::fromType<double>()); return (double) dblbuf; @@ -620,7 +577,7 @@ static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true (SQLPOINTER) &lngbuf, sizeof(lngbuf), &lengthIndicator); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) + if (!SQL_SUCCEEDED(r)) return QVariant(); if (lengthIndicator == SQL_NULL_DATA) return QVariant(QMetaType::fromType<qlonglong>()); @@ -636,28 +593,25 @@ static bool isAutoValue(const SQLHANDLE hStmt, int column) SQLLEN nNumericAttribute = 0; // Check for auto-increment const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE, 0, 0, 0, &nNumericAttribute); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QStringLiteral("qMakeField: Unable to get autovalue attribute for column ") - + QString::number(column), hStmt); + if (!SQL_SUCCEEDED(r)) { + qSqlWarning(("QODBC::isAutoValue: Unable to get autovalue attribute for column %1"_L1) + .arg(QString::number(column)), hStmt); return false; } return nNumericAttribute != SQL_FALSE; } -static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage); - // creates a QSqlField from a valid hStmt generated // by SQLColumns. The hStmt has to point to a valid position. static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p) { - QString fname = qGetStringData(hStmt, 3, -1, p->unicode); + QString fname = qGetStringData(hStmt, 3, -1, p->unicode).toString(); int type = qGetIntData(hStmt, 4).toInt(); // column type QSqlField f(fname, qDecodeODBCType(type, p)); QVariant var = qGetIntData(hStmt, 6); f.setLength(var.isNull() ? -1 : var.toInt()); // column size var = qGetIntData(hStmt, 8).toInt(); f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision - f.setSqlType(type); int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN if (required == SQL_NO_NULLS) @@ -668,16 +622,7 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* return f; } -static QSqlField qMakeFieldInfo(const QODBCResultPrivate* p, int i ) -{ - QString errorMessage; - const QSqlField result = qMakeFieldInfo(p->hStmt, i, &errorMessage); - if (!errorMessage.isEmpty()) - qSqlWarning(errorMessage, p); - return result; -} - -static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage) +static QSqlField qMakeFieldInfo(const QODBCResultPrivate *p, int i) { SQLSMALLINT colNameLen; SQLSMALLINT colType; @@ -685,12 +630,10 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess SQLSMALLINT colScale; SQLSMALLINT nullable; SQLRETURN r = SQL_ERROR; - QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); - errorMessage->clear(); - r = SQLDescribeCol(hStmt, + QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE); + r = SQLDescribeCol(p->hStmt, i+1, - colName.data(), - (SQLSMALLINT)COLNAMESIZE, + colName.data(), SQLSMALLINT(colName.size()), &colNameLen, &colType, &colSize, @@ -698,12 +641,13 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess &nullable); if (r != SQL_SUCCESS) { - *errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i); + qSqlWarning(("QODBC::qMakeFieldInfo: Unable to describe column %1"_L1) + .arg(QString::number(i)), p); return QSqlField(); } SQLLEN unsignedFlag = SQL_FALSE; - r = SQLColAttribute (hStmt, + r = SQLColAttribute (p->hStmt, i + 1, SQL_DESC_UNSIGNED, 0, @@ -711,15 +655,14 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess 0, &unsignedFlag); if (r != SQL_SUCCESS) { - qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ") - + QString::number(i), hStmt); + qSqlWarning(("QODBC::qMakeFieldInfo: Unable to get column attributes for column %1"_L1) + .arg(QString::number(i)), p); } const QString qColName(fromSQLTCHAR(colName, colNameLen)); // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN QMetaType type = qDecodeODBCType(colType, unsignedFlag == SQL_FALSE); QSqlField f(qColName, type); - f.setSqlType(colType); f.setLength(colSize == 0 ? -1 : int(colSize)); f.setPrecision(colScale == 0 ? -1 : int(colScale)); if (nullable == SQL_NO_NULLS) @@ -727,19 +670,24 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess else if (nullable == SQL_NULLABLE) f.setRequired(false); // else we don't know - f.setAutoValue(isAutoValue(hStmt, i)); - QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE); + f.setAutoValue(isAutoValue(p->hStmt, i)); + QVarLengthArray<SQLTCHAR, TABLENAMESIZE> tableName(TABLENAMESIZE); SQLSMALLINT tableNameLen; - r = SQLColAttribute(hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName.data(), - TABLENAMESIZE, &tableNameLen, 0); + r = SQLColAttribute(p->hStmt, + i + 1, + SQL_DESC_BASE_TABLE_NAME, + tableName.data(), + SQLSMALLINT(tableName.size() * sizeof(SQLTCHAR)), // SQLColAttribute needs/returns size in bytes + &tableNameLen, + 0); if (r == SQL_SUCCESS) - f.setTableName(fromSQLTCHAR(tableName, tableNameLen)); + f.setTableName(fromSQLTCHAR(tableName, tableNameLen / sizeof(SQLTCHAR))); return f; } static size_t qGetODBCVersion(const QString &connOpts) { - if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive)) + if (connOpts.contains("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"_L1, Qt::CaseInsensitive)) return SQL_OV_ODBC3; return SQL_OV_ODBC2; } @@ -754,185 +702,202 @@ QChar QODBCDriverPrivate::quoteChar() &driverResponse, sizeof(driverResponse), &length); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + if (SQL_SUCCEEDED(r)) quote = QChar(driverResponse[0]); else - quote = QLatin1Char('"'); + quote = u'"'; isQuoteInitialized = true; } return quote; } +SQLRETURN QODBCDriverPrivate::sqlFetchNext(const SqlStmtHandle &hStmt) const +{ + return sqlFetchNext(hStmt.handle()); +} + +SQLRETURN QODBCDriverPrivate::sqlFetchNext(SQLHANDLE hStmt) const +{ + if (hasSQLFetchScroll) + return SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); + return SQLFetch(hStmt); +} -bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) +static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, QStringView val) +{ + auto encoded = toSQLTCHAR(val); + return SQLSetConnectAttr(handle, attr, + encoded.data(), + SQLINTEGER(encoded.size() * sizeof(SQLTCHAR))); // size in bytes +} + + +bool QODBCDriverPrivate::setConnectionOptions(const QString &connOpts) { // Set any connection attributes - const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts)); SQLRETURN r = SQL_SUCCESS; - for (int i = 0; i < opts.count(); ++i) { - const QString tmp(opts.at(i)); + for (const auto connOpt : QStringTokenizer{connOpts, u';'}) { int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { - qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\''; + if ((idx = connOpt.indexOf(u'=')) == -1) { + qSqlWarning(("QODBCDriver::open: Illegal connect option value '%1'"_L1) + .arg(connOpt), this); continue; } - const QString opt(tmp.left(idx)); - const QString val(tmp.mid(idx + 1).simplified()); + const auto opt(connOpt.left(idx)); + const auto val(connOpt.mid(idx + 1).trimmed()); SQLUINTEGER v = 0; r = SQL_SUCCESS; - if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) { - if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) { + if (opt == "SQL_ATTR_ACCESS_MODE"_L1) { + if (val == "SQL_MODE_READ_ONLY"_L1) { v = SQL_MODE_READ_ONLY; - } else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) { + } else if (val == "SQL_MODE_READ_WRITE"_L1) { v = SQL_MODE_READ_WRITE; } else { - qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1) + .arg(val), this); continue; } r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) { + } else if (opt == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) { v = val.toUInt(); r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { + } else if (opt == "SQL_ATTR_LOGIN_TIMEOUT"_L1) { v = val.toUInt(); r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) { - val.utf16(); // 0 terminate - r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, - toSQLTCHAR(val).data(), - SQLINTEGER(val.length() * sizeof(SQLTCHAR))); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) { - if (val.toUpper() == QLatin1String("SQL_TRUE")) { + } else if (opt == "SQL_ATTR_CURRENT_CATALOG"_L1) { + r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val); + } else if (opt == "SQL_ATTR_METADATA_ID"_L1) { + if (val == "SQL_TRUE"_L1) { v = SQL_TRUE; - } else if (val.toUpper() == QLatin1String("SQL_FALSE")) { + } else if (val == "SQL_FALSE"_L1) { v = SQL_FALSE; } else { - qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1) + .arg(val), this); continue; } r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) { + } else if (opt == "SQL_ATTR_PACKET_SIZE"_L1) { v = val.toUInt(); r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) { - val.utf16(); // 0 terminate - r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, - toSQLTCHAR(val).data(), - SQLINTEGER(val.length() * sizeof(SQLTCHAR))); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) { - if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) { + } else if (opt == "SQL_ATTR_TRACEFILE"_L1) { + r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val); + } else if (opt == "SQL_ATTR_TRACE"_L1) { + if (val == "SQL_OPT_TRACE_OFF"_L1) { v = SQL_OPT_TRACE_OFF; - } else if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_ON")) { + } else if (val == "SQL_OPT_TRACE_ON"_L1) { v = SQL_OPT_TRACE_ON; } else { - qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1) + .arg(val), this); continue; } r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_POOLING")) { - if (val == QLatin1String("SQL_CP_OFF")) + } else if (opt == "SQL_ATTR_CONNECTION_POOLING"_L1) { + if (val == "SQL_CP_OFF"_L1) v = SQL_CP_OFF; - else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_DRIVER")) + else if (val == "SQL_CP_ONE_PER_DRIVER"_L1) v = SQL_CP_ONE_PER_DRIVER; - else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_HENV")) + else if (val == "SQL_CP_ONE_PER_HENV"_L1) v = SQL_CP_ONE_PER_HENV; - else if (val.toUpper() == QLatin1String("SQL_CP_DEFAULT")) + else if (val == "SQL_CP_DEFAULT"_L1) v = SQL_CP_DEFAULT; else { - qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1) + .arg(val), this); continue; } r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) { - if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH")) + } else if (opt == "SQL_ATTR_CP_MATCH"_L1) { + if (val == "SQL_CP_STRICT_MATCH"_L1) v = SQL_CP_STRICT_MATCH; - else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH")) + else if (val == "SQL_CP_RELAXED_MATCH"_L1) v = SQL_CP_RELAXED_MATCH; - else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT")) + else if (val == "SQL_CP_MATCH_DEFAULT"_L1) v = SQL_CP_MATCH_DEFAULT; else { - qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; + qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1) + .arg(val), this); continue; } r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0); - } else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) { + } else if (opt == "SQL_ATTR_ODBC_VERSION"_L1) { // Already handled in QODBCDriver::open() continue; } else { - qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\''; + qSqlWarning(("QODBCDriver::open: Unknown connection attribute '%1'"_L1) + .arg(opt), this); } - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) - qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg( - opt), this); + if (!SQL_SUCCEEDED(r)) + qSqlWarning(("QODBCDriver::open: Unable to set connection attribute '%1'"_L1) + .arg(opt), this); } return true; } -void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog, - QString &schema, QString &table) +void QODBCDriverPrivate::splitTableQualifier(const QString &qualifier, QString &catalog, + QString &schema, QString &table) const { + Q_Q(const QODBCDriver); + const auto adjustName = [&](const QString &name) { + if (q->isIdentifierEscaped(name, QSqlDriver::TableName)) + return q->stripDelimiters(name, QSqlDriver::TableName); + return adjustCase(name); + }; + catalog.clear(); + schema.clear(); + table.clear(); if (!useSchema) { - table = qualifier; + table = adjustName(qualifier); return; } - QStringList l = qualifier.split(QLatin1Char('.')); - if (l.count() > 3) - return; // can't possibly be a valid table qualifier - int i = 0, n = l.count(); - if (n == 1) { - table = qualifier; - } else { - for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) { - if (n == 3) { - if (i == 0) { - catalog = *it; - } else if (i == 1) { - schema = *it; - } else if (i == 2) { - table = *it; - } - } else if (n == 2) { - if (i == 0) { - schema = *it; - } else if (i == 1) { - table = *it; - } - } - i++; - } + const QList<QStringView> l = QStringView(qualifier).split(u'.'); + switch (l.count()) { + case 1: + table = adjustName(qualifier); + break; + case 2: + schema = adjustName(l.at(0).toString()); + table = adjustName(l.at(1).toString()); + break; + case 3: + catalog = adjustName(l.at(0).toString()); + schema = adjustName(l.at(1).toString()); + table = adjustName(l.at(2).toString()); + break; + default: + qSqlWarning(("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'"_L1) + .arg(qualifier), this); + break; } } -QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const +void QODBCDriverPrivate::checkDefaultCase() { - DefaultCase ret; + m_defaultCase = DefaultCase::Mixed; //arbitrary case if driver cannot be queried SQLUSMALLINT casing; - int r = SQLGetInfo(hDbc, - SQL_IDENTIFIER_CASE, - &casing, - sizeof(casing), - NULL); - if ( r != SQL_SUCCESS) - ret = Mixed;//arbitrary case if driver cannot be queried - else { + SQLRETURN r = SQLGetInfo(hDbc, + SQL_IDENTIFIER_CASE, + &casing, + sizeof(casing), + NULL); + if (r == SQL_SUCCESS) { switch (casing) { - case (SQL_IC_UPPER): - ret = Upper; - break; - case (SQL_IC_LOWER): - ret = Lower; - break; - case (SQL_IC_SENSITIVE): - ret = Sensitive; - break; - case (SQL_IC_MIXED): - default: - ret = Mixed; - break; + case SQL_IC_UPPER: + m_defaultCase = DefaultCase::Upper; + break; + case SQL_IC_LOWER: + m_defaultCase = DefaultCase::Lower; + break; + case SQL_IC_SENSITIVE: + m_defaultCase = DefaultCase::Sensitive; + break; + case SQL_IC_MIXED: + m_defaultCase = DefaultCase::Mixed; + break; } } - return ret; } /* @@ -941,20 +906,16 @@ QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const */ QString QODBCDriverPrivate::adjustCase(const QString &identifier) const { - QString ret = identifier; - switch(defaultCase()) { - case (Lower): - ret = identifier.toLower(); - break; - case (Upper): - ret = identifier.toUpper(); - break; - case(Mixed): - case(Sensitive): - default: - ret = identifier; + switch (m_defaultCase) { + case DefaultCase::Lower: + return identifier.toLower(); + case DefaultCase::Upper: + return identifier.toUpper(); + case DefaultCase::Mixed: + case DefaultCase::Sensitive: + break; } - return ret; + return identifier; } //////////////////////////////////////////////////////////////////////////// @@ -970,8 +931,7 @@ QODBCResult::~QODBCResult() if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) { SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") - + QString::number(r), d); + qSqlWarning(("QODBCResult: Unable to free statement handle "_L1), d); } } @@ -990,7 +950,7 @@ bool QODBCResult::reset (const QString& query) if (d->hStmt && d->isStmtHandleValid()) { r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d); + qSqlWarning("QODBCResult::reset: Unable to free statement handle"_L1, d); return false; } } @@ -998,7 +958,7 @@ bool QODBCResult::reset (const QString& query) d->dpDbc(), &d->hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d); + qSqlWarning("QODBCResult::reset: Unable to allocate statement handle"_L1, d); return false; } @@ -1015,17 +975,20 @@ bool QODBCResult::reset (const QString& query) (SQLPOINTER)SQL_CURSOR_STATIC, SQL_IS_UINTEGER); } - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (!SQL_SUCCEEDED(r)) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. " "Please check your ODBC driver configuration"), QSqlError::StatementError, d)); return false; } - r = SQLExecDirect(d->hStmt, - toSQLTCHAR(query).data(), - (SQLINTEGER) query.length()); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) { + { + auto encoded = toSQLTCHAR(query); + r = SQLExecDirect(d->hStmt, + encoded.data(), + SQLINTEGER(encoded.size())); + } + if (!SQL_SUCCEEDED(r) && r!= SQL_NO_DATA) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to execute statement"), QSqlError::StatementError, d)); return false; @@ -1033,14 +996,14 @@ bool QODBCResult::reset (const QString& query) SQLULEN isScrollable = 0; r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0); - if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + if (SQL_SUCCEEDED(r)) setForwardOnly(isScrollable == SQL_NONSCROLLABLE); SQLSMALLINT count = 0; SQLNumResultCols(d->hStmt, &count); if (count) { setSelect(true); - for (int i = 0; i < count; ++i) { + for (SQLSMALLINT i = 0; i < count; ++i) { d->rInf.append(qMakeFieldInfo(d, i)); } d->fieldCache.resize(count); @@ -1102,7 +1065,7 @@ bool QODBCResult::fetchNext() else r = SQLFetch(d->hStmt); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (!SQL_SUCCEEDED(r)) { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to fetch next"), QSqlError::ConnectionError, d)); @@ -1199,7 +1162,8 @@ QVariant QODBCResult::data(int field) { Q_D(QODBCResult); if (field >= d->rInf.count() || field < 0) { - qWarning() << "QODBCResult::data: column" << field << "out of range"; + qSqlWarning(("QODBCResult::data: column %1 out of range"_L1) + .arg(QString::number(field)), d); return QVariant(); } if (field < d->fieldCacheIdx) @@ -1213,19 +1177,21 @@ QVariant QODBCResult::data(int field) // fetched column n+1, so cache all previous columns here const QSqlField info = d->rInf.field(i); switch (info.metaType().id()) { - case QVariant::LongLong: + case QMetaType::LongLong: d->fieldCache[i] = qGetBigIntData(d->hStmt, i); break; - case QVariant::ULongLong: + case QMetaType::ULongLong: d->fieldCache[i] = qGetBigIntData(d->hStmt, i, false); break; - case QVariant::Int: + case QMetaType::Int: + case QMetaType::Short: d->fieldCache[i] = qGetIntData(d->hStmt, i); - break; - case QVariant::UInt: + break; + case QMetaType::UInt: + case QMetaType::UShort: d->fieldCache[i] = qGetIntData(d->hStmt, i, false); break; - case QVariant::Date: + case QMetaType::QDate: DATE_STRUCT dbuf; r = SQLGetData(d->hStmt, i + 1, @@ -1233,12 +1199,12 @@ QVariant QODBCResult::data(int field) (SQLPOINTER)&dbuf, 0, &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) + if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA)) d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day)); else d->fieldCache[i] = QVariant(QMetaType::fromType<QDate>()); break; - case QVariant::Time: + case QMetaType::QTime: TIME_STRUCT tbuf; r = SQLGetData(d->hStmt, i + 1, @@ -1246,12 +1212,12 @@ QVariant QODBCResult::data(int field) (SQLPOINTER)&tbuf, 0, &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) + if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA)) d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second)); else d->fieldCache[i] = QVariant(QMetaType::fromType<QTime>()); break; - case QVariant::DateTime: + case QMetaType::QDateTime: TIMESTAMP_STRUCT dtbuf; r = SQLGetData(d->hStmt, i + 1, @@ -1259,19 +1225,19 @@ QVariant QODBCResult::data(int field) (SQLPOINTER)&dtbuf, 0, &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) + if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA)) d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day), QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000))); else d->fieldCache[i] = QVariant(QMetaType::fromType<QDateTime>()); break; - case QVariant::ByteArray: + case QMetaType::QByteArray: d->fieldCache[i] = qGetBinaryData(d->hStmt, i); break; - case QVariant::String: + case QMetaType::QString: d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode); break; - case QVariant::Double: + case QMetaType::Double: switch(numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: d->fieldCache[i] = qGetIntData(d->hStmt, i); @@ -1283,12 +1249,13 @@ QVariant QODBCResult::data(int field) d->fieldCache[i] = qGetDoubleData(d->hStmt, i); break; case QSql::HighPrecision: - d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false); + const int extra = info.precision() > 0 ? 1 : 0; + d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length() + extra, false); break; } break; default: - d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false)); + d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false); break; } d->fieldCacheIdx = field + 1; @@ -1301,7 +1268,7 @@ bool QODBCResult::isNull(int field) Q_D(const QODBCResult); if (field < 0 || field >= d->fieldCache.size()) return true; - if (field <= d->fieldCacheIdx) { + if (field >= d->fieldCacheIdx) { // since there is no good way to find out whether the value is NULL // without fetching the field we'll fetch it here. // (data() also sets the NULL flag) @@ -1322,8 +1289,7 @@ int QODBCResult::numRowsAffected() SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount); if (r == SQL_SUCCESS) return affectedRowCount; - else - qSqlWarning(QLatin1String("QODBCResult::numRowsAffected: Unable to count affected rows"), d); + qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d); return -1; } @@ -1338,7 +1304,7 @@ bool QODBCResult::prepare(const QString& query) if (d->hStmt && d->isStmtHandleValid()) { r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d); + qSqlWarning("QODBCResult::prepare: Unable to close statement"_L1, d); return false; } } @@ -1346,7 +1312,7 @@ bool QODBCResult::prepare(const QString& query) d->dpDbc(), &d->hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d); + qSqlWarning("QODBCResult::prepare: Unable to allocate statement handle"_L1, d); return false; } @@ -1363,16 +1329,19 @@ bool QODBCResult::prepare(const QString& query) (SQLPOINTER)SQL_CURSOR_STATIC, SQL_IS_UINTEGER); } - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + if (!SQL_SUCCEEDED(r)) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. " "Please check your ODBC driver configuration"), QSqlError::StatementError, d)); return false; } - r = SQLPrepare(d->hStmt, - toSQLTCHAR(query).data(), - (SQLINTEGER) query.length()); + { + auto encoded = toSQLTCHAR(query); + r = SQLPrepare(d->hStmt, + encoded.data(), + SQLINTEGER(encoded.size())); + } if (r != SQL_SUCCESS) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", @@ -1392,7 +1361,7 @@ bool QODBCResult::exec() d->fieldCacheIdx = 0; if (!d->hStmt) { - qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d); + qSqlWarning("QODBCResult::exec: No statement handle available"_L1, d); return false; } @@ -1400,22 +1369,20 @@ bool QODBCResult::exec() SQLCloseCursor(d->hStmt); QVariantList &values = boundValues(); - QByteArrayList tmpStorage(values.count(), QByteArray()); // holds temporary buffers - QVarLengthArray<SQLLEN, 32> indicators(values.count()); - memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN)); + QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter() + QVarLengthArray<SQLLEN, 32> indicators(values.count(), 0); // bind parameters - only positional binding allowed - int i; SQLRETURN r; - for (i = 0; i < values.count(); ++i) { + for (qsizetype i = 0; i < values.count(); ++i) { if (bindValueType(i) & QSql::Out) values[i].detach(); const QVariant &val = values.at(i); SQLLEN *ind = &indicators[i]; - if (val.isNull()) + if (QSqlResultPrivate::isVariantNull(val)) *ind = SQL_NULL_DATA; - switch (val.userType()) { - case QVariant::Date: { + switch (val.typeId()) { + case QMetaType::QDate: { QByteArray &ba = tmpStorage[i]; ba.resize(sizeof(DATE_STRUCT)); DATE_STRUCT *dt = (DATE_STRUCT *)const_cast<char *>(ba.constData()); @@ -1434,7 +1401,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; } - case QVariant::Time: { + case QMetaType::QTime: { QByteArray &ba = tmpStorage[i]; ba.resize(sizeof(TIME_STRUCT)); TIME_STRUCT *dt = (TIME_STRUCT *)const_cast<char *>(ba.constData()); @@ -1453,7 +1420,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; } - case QVariant::DateTime: { + case QMetaType::QDateTime: { QByteArray &ba = tmpStorage[i]; ba.resize(sizeof(TIMESTAMP_STRUCT)); TIMESTAMP_STRUCT *dt = reinterpret_cast<TIMESTAMP_STRUCT *>(const_cast<char *>(ba.constData())); @@ -1489,7 +1456,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; } - case QVariant::Int: + case QMetaType::Int: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1501,7 +1468,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::UInt: + case QMetaType::UInt: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1513,7 +1480,31 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::Double: + case QMetaType::Short: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_SSHORT, + SQL_SMALLINT, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QMetaType::UShort: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_USHORT, + SQL_NUMERIC, + 15, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QMetaType::Double: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1525,7 +1516,19 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::LongLong: + case QMetaType::Float: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[bindValueType(i) & QSql::InOut], + SQL_C_FLOAT, + SQL_REAL, + 0, + 0, + const_cast<void *>(val.constData()), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QMetaType::LongLong: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1537,7 +1540,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::ULongLong: + case QMetaType::ULongLong: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1549,7 +1552,7 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::ByteArray: + case QMetaType::QByteArray: if (*ind != SQL_NULL_DATA) { *ind = val.toByteArray().size(); } @@ -1564,7 +1567,7 @@ bool QODBCResult::exec() val.toByteArray().size(), ind); break; - case QVariant::Bool: + case QMetaType::Bool: r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], @@ -1576,39 +1579,39 @@ bool QODBCResult::exec() 0, *ind == SQL_NULL_DATA ? ind : NULL); break; - case QVariant::String: + case QMetaType::QString: if (d->unicode) { QByteArray &ba = tmpStorage[i]; - QString str = val.toString(); + { + const auto encoded = toSQLTCHAR(val.toString()); + ba = QByteArray(reinterpret_cast<const char *>(encoded.data()), + encoded.size() * sizeof(SQLTCHAR)); + } + if (*ind != SQL_NULL_DATA) - *ind = str.length() * sizeof(SQLTCHAR); - const qsizetype strSize = str.length() * sizeof(SQLTCHAR); + *ind = ba.size(); if (bindValueType(i) & QSql::Out) { - const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str)); - ba = QByteArray((const char *)a.constData(), int(a.size() * sizeof(SQLTCHAR))); r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], SQL_C_TCHAR, - strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, 0, // god knows... don't change this! 0, - ba.data(), + const_cast<char *>(ba.constData()), // don't detach ba.size(), ind); break; } - ba = QByteArray(reinterpret_cast<const char *>(toSQLTCHAR(str).constData()), - int(strSize)); r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], SQL_C_TCHAR, - strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, - strSize, + ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + ba.size(), 0, - const_cast<char *>(ba.constData()), + const_cast<char *>(ba.constData()), // don't detach ba.size(), ind); break; @@ -1633,7 +1636,7 @@ bool QODBCResult::exec() ind); break; } - // fall through + Q_FALLTHROUGH(); default: { QByteArray &ba = tmpStorage[i]; if (*ind != SQL_NULL_DATA) @@ -1651,15 +1654,15 @@ bool QODBCResult::exec() break; } } if (r != SQL_SUCCESS) { - qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d); + qSqlWarning("QODBCResult::exec: unable to bind variable:"_L1, d); setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to bind variable"), QSqlError::StatementError, d)); return false; } } r = SQLExecute(d->hStmt); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) { - qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d); + if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) { + qSqlWarning("QODBCResult::exec: Unable to execute statement:"_L1, d); setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to execute statement"), QSqlError::StatementError, d)); return false; @@ -1667,14 +1670,14 @@ bool QODBCResult::exec() SQLULEN isScrollable = 0; r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0); - if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + if (SQL_SUCCEEDED(r)) setForwardOnly(isScrollable == SQL_NONSCROLLABLE); SQLSMALLINT count = 0; SQLNumResultCols(d->hStmt, &count); if (count) { setSelect(true); - for (int i = 0; i < count; ++i) { + for (SQLSMALLINT i = 0; i < count; ++i) { d->rInf.append(qMakeFieldInfo(d, i)); } d->fieldCache.resize(count); @@ -1688,42 +1691,46 @@ bool QODBCResult::exec() if (!hasOutValues()) return true; - for (i = 0; i < values.count(); ++i) { - switch (values.at(i).userType()) { - case QVariant::Date: { + for (qsizetype i = 0; i < values.count(); ++i) { + switch (values.at(i).typeId()) { + case QMetaType::QDate: { DATE_STRUCT ds = *((DATE_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData())); values[i] = QVariant(QDate(ds.year, ds.month, ds.day)); break; } - case QVariant::Time: { + case QMetaType::QTime: { TIME_STRUCT dt = *((TIME_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData())); values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second)); break; } - case QVariant::DateTime: { + case QMetaType::QDateTime: { TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*) const_cast<char *>(tmpStorage.at(i).constData())); values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day), QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000))); break; } - case QVariant::Bool: - case QVariant::Int: - case QVariant::UInt: - case QVariant::Double: - case QVariant::ByteArray: - case QVariant::LongLong: - case QVariant::ULongLong: + case QMetaType::Bool: + case QMetaType::Short: + case QMetaType::UShort: + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::Float: + case QMetaType::Double: + case QMetaType::QByteArray: + case QMetaType::LongLong: + case QMetaType::ULongLong: //nothing to do break; - case QVariant::String: + case QMetaType::QString: if (d->unicode) { if (bindValueType(i) & QSql::Out) { - const QByteArray &first = tmpStorage.at(i); - QVarLengthArray<SQLTCHAR> array; - array.append((const SQLTCHAR *)first.constData(), first.size()); - values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR)); + const QByteArray &bytes = tmpStorage.at(i); + const auto strSize = bytes.size() / sizeof(SQLTCHAR); + QVarLengthArray<SQLTCHAR> string(strSize); + memcpy(string.data(), bytes.data(), strSize * sizeof(SQLTCHAR)); + values[i] = fromSQLTCHAR(string); } break; } - // fall through + Q_FALLTHROUGH(); default: { if (bindValueType(i) & QSql::Out) values[i] = tmpStorage.at(i); @@ -1751,13 +1758,13 @@ QVariant QODBCResult::lastInsertId() const switch (driver()->dbmsType()) { case QSqlDriver::MSSqlServer: case QSqlDriver::Sybase: - sql = QLatin1String("SELECT @@IDENTITY;"); + sql = "SELECT @@IDENTITY;"_L1; break; case QSqlDriver::MySqlServer: - sql = QLatin1String("SELECT LAST_INSERT_ID();"); + sql = "SELECT LAST_INSERT_ID();"_L1; break; case QSqlDriver::PostgreSQL: - sql = QLatin1String("SELECT lastval();"); + sql = "SELECT lastval();"_L1; break; default: break; @@ -1768,9 +1775,9 @@ QVariant QODBCResult::lastInsertId() const if (qry.exec(sql) && qry.next()) return qry.value(0); - qSqlWarning(QLatin1String("QODBCResult::lastInsertId: Unable to get lastInsertId"), d); + qSqlWarning("QODBCResult::lastInsertId: Unable to get lastInsertId"_L1, d); } else { - qSqlWarning(QLatin1String("QODBCResult::lastInsertId: not implemented for this DBMS"), d); + qSqlWarning("QODBCResult::lastInsertId: not implemented for this DBMS"_L1, d); } return QVariant(); @@ -1795,9 +1802,7 @@ bool QODBCResult::nextResult() SQLRETURN r = SQLMoreResults(d->hStmt); if (r != SQL_SUCCESS) { if (r == SQL_SUCCESS_WITH_INFO) { - int nativeCode = -1; - QString message = qODBCWarn(d, &nativeCode); - qWarning() << "QODBCResult::nextResult():" << message; + qSqlWarning("QODBCResult::nextResult:"_L1, d); } else { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QODBCResult", @@ -1810,7 +1815,7 @@ bool QODBCResult::nextResult() SQLNumResultCols(d->hStmt, &count); if (count) { setSelect(true); - for (int i = 0; i < count; ++i) { + for (SQLSMALLINT i = 0; i < count; ++i) { d->rInf.append(qMakeFieldInfo(d, i)); } d->fieldCache.resize(count); @@ -1916,6 +1921,18 @@ bool QODBCDriver::open(const QString & db, int, const QString& connOpts) { + const auto ensureEscaped = [](QString arg) -> QString { + QChar quoteChar; + if (arg.startsWith(u'"')) + quoteChar = u'\''; + else if (arg.startsWith(u'\'')) + quoteChar = u'"'; + else if (arg.contains(u';')) + quoteChar = u'"'; + else + return arg; + return quoteChar + arg + quoteChar; + }; Q_D(QODBCDriver); if (isOpen()) close(); @@ -1923,8 +1940,8 @@ bool QODBCDriver::open(const QString & db, r = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &d->hEnv); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d); + if (!SQL_SUCCEEDED(r)) { + qSqlWarning("QODBCDriver::open: Unable to allocate environment"_L1, d); setOpenError(true); return false; } @@ -1935,8 +1952,8 @@ bool QODBCDriver::open(const QString & db, r = SQLAllocHandle(SQL_HANDLE_DBC, d->hEnv, &d->hDbc); - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d); + if (!SQL_SUCCEEDED(r)) { + qSqlWarning("QODBCDriver::open: Unable to allocate connection"_L1, d); setOpenError(true); cleanup(); return false; @@ -1950,32 +1967,32 @@ bool QODBCDriver::open(const QString & db, // Create the connection string QString connQStr; // support the "DRIVER={SQL SERVER};SERVER=blah" syntax - if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive)) - connQStr = QLatin1String("FILEDSN=") + db; - else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive) - || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive)) + if (db.contains(".dsn"_L1, Qt::CaseInsensitive)) + connQStr = "FILEDSN="_L1 + ensureEscaped(db); + else if (db.contains("DRIVER="_L1, Qt::CaseInsensitive) + || db.contains("SERVER="_L1, Qt::CaseInsensitive)) connQStr = db; else - connQStr = QLatin1String("DSN=") + db; + connQStr = "DSN="_L1 + ensureEscaped(db); if (!user.isEmpty()) - connQStr += QLatin1String(";UID=") + user; + connQStr += ";UID="_L1 + ensureEscaped(user); if (!password.isEmpty()) - connQStr += QLatin1String(";PWD=") + password; + connQStr += ";PWD="_L1 + ensureEscaped(password); SQLSMALLINT cb; - QVarLengthArray<SQLTCHAR> connOut(1024); - memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR)); - r = SQLDriverConnect(d->hDbc, - NULL, - toSQLTCHAR(connQStr).data(), - (SQLSMALLINT)connQStr.length(), - connOut.data(), - 1024, - &cb, - /*SQL_DRIVER_NOPROMPT*/0); - - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + QVarLengthArray<SQLTCHAR, 1024> connOut(1024); + { + auto encoded = toSQLTCHAR(connQStr); + r = SQLDriverConnect(d->hDbc, + nullptr, + encoded.data(), SQLSMALLINT(encoded.size()), + connOut.data(), SQLSMALLINT(connOut.size()), + &cb, + /*SQL_DRIVER_NOPROMPT*/0); + } + + if (!SQL_SUCCEEDED(r)) { setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); setOpenError(true); cleanup(); @@ -1996,11 +2013,12 @@ bool QODBCDriver::open(const QString & db, d->checkHasSQLFetchScroll(); d->checkHasMultiResults(); d->checkDateTimePrecision(); + d->checkDefaultCase(); setOpen(true); setOpenError(false); if (d->dbmsType == MSSqlServer) { QSqlQuery i(createResult()); - i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON")); + i.exec("SET QUOTED_IDENTIFIER ON"_L1); } return true; } @@ -2017,26 +2035,26 @@ void QODBCDriver::cleanup() Q_D(QODBCDriver); SQLRETURN r; - if(d->hDbc) { + if (d->hDbc) { // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect if (isOpen()) { r = SQLDisconnect(d->hDbc); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d); + qSqlWarning("QODBCDriver::disconnect: Unable to disconnect datasource"_L1, d); else d->disconnectCount++; } r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d); + qSqlWarning("QODBCDriver::cleanup: Unable to free connection handle"_L1, d); d->hDbc = 0; } if (d->hEnv) { r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d); + qSqlWarning("QODBCDriver::cleanup: Unable to free environment handle"_L1, d); d->hEnv = 0; } } @@ -2054,7 +2072,7 @@ void QODBCDriverPrivate::checkUnicode() (SQLPOINTER)&fFunc, sizeof(fFunc), NULL); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) { + if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WCHAR)) { unicode = true; return; } @@ -2064,7 +2082,7 @@ void QODBCDriverPrivate::checkUnicode() (SQLPOINTER)&fFunc, sizeof(fFunc), NULL); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) { + if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WVARCHAR)) { unicode = true; return; } @@ -2074,74 +2092,86 @@ void QODBCDriverPrivate::checkUnicode() (SQLPOINTER)&fFunc, sizeof(fFunc), NULL); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) { + if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WLONGVARCHAR)) { unicode = true; return; } - SQLHANDLE hStmt; - r = SQLAllocHandle(SQL_HANDLE_STMT, - hDbc, - &hStmt); - - r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS); - if(r == SQL_SUCCESS) { - r = SQLFetch(hStmt); - if(r == SQL_SUCCESS) { - QVarLengthArray<SQLWCHAR> buffer(10); - r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL); - if(r == SQL_SUCCESS && fromSQLTCHAR(buffer) == QLatin1String("test")) { + + SqlStmtHandle hStmt(hDbc); + // for databases which do not return something useful in SQLGetInfo and are picky about a + // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle + const std::array<QStringView, 3> statements = { + u"select 'test'", + u"values('test')", + u"select 'test' from dual", + }; + for (const auto &statement : statements) { + auto encoded = toSQLTCHAR(statement); + r = SQLExecDirect(hStmt.handle(), encoded.data(), SQLINTEGER(encoded.size())); + if (r == SQL_SUCCESS) + break; + } + if (r == SQL_SUCCESS) { + r = SQLFetch(hStmt.handle()); + if (r == SQL_SUCCESS) { + QVarLengthArray<SQLWCHAR, 10> buffer(10); + r = SQLGetData(hStmt.handle(), 1, SQL_C_WCHAR, buffer.data(), + buffer.size() * sizeof(SQLWCHAR), NULL); + if (r == SQL_SUCCESS && fromSQLTCHAR(buffer) == "test"_L1) { unicode = true; } } } - r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); } bool QODBCDriverPrivate::checkDriver() const { #ifdef ODBC_CHECK_DRIVER - static const SQLUSMALLINT reqFunc[] = { + static constexpr SQLUSMALLINT reqFunc[] = { SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS, SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT, - SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0 + SQL_API_SQLGETINFO, SQL_API_SQLTABLES }; // these functions are optional - static const SQLUSMALLINT optFunc[] = { - SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0 + static constexpr SQLUSMALLINT optFunc[] = { + SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT }; SQLRETURN r; SQLUSMALLINT sup; - int i; // check the required functions - for (i = 0; reqFunc[i] != 0; ++i) { + for (const SQLUSMALLINT func : reqFunc) { - r = SQLGetFunctions(hDbc, reqFunc[i], &sup); + r = SQLGetFunctions(hDbc, func, &sup); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); + qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this); return false; } if (sup == SQL_FALSE) { - qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] << - ").\nPlease look at the Qt SQL Module Driver documentation for more information."; + qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support all needed " + "functionality (func id %1).\nPlease look at the Qt SQL Module " + "Driver documentation for more information."_L1) + .arg(QString::number(func)), this); return false; } } // these functions are optional and just generate a warning - for (i = 0; optFunc[i] != 0; ++i) { + for (const SQLUSMALLINT func : optFunc) { - r = SQLGetFunctions(hDbc, optFunc[i], &sup); + r = SQLGetFunctions(hDbc, func, &sup); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); + qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this); return false; } if (sup == SQL_FALSE) { - qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')'; + qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support some " + "non-critical functions (func id %1)."_L1) + .arg(QString::number(func)), this); return true; } } @@ -2160,33 +2190,32 @@ void QODBCDriverPrivate::checkSchemaUsage() (SQLPOINTER) &val, sizeof(val), NULL); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) + if (SQL_SUCCEEDED(r)) useSchema = (val != 0); } void QODBCDriverPrivate::checkDBMS() { SQLRETURN r; - QVarLengthArray<SQLTCHAR> serverString(200); + QVarLengthArray<SQLTCHAR, 200> serverString(200); SQLSMALLINT t; - memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR)); r = SQLGetInfo(hDbc, SQL_DBMS_NAME, serverString.data(), SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)), &t); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (SQL_SUCCEEDED(r)) { const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR)); - if (serverType.contains(QLatin1String("PostgreSQL"), Qt::CaseInsensitive)) + if (serverType.contains("PostgreSQL"_L1, Qt::CaseInsensitive)) dbmsType = QSqlDriver::PostgreSQL; - else if (serverType.contains(QLatin1String("Oracle"), Qt::CaseInsensitive)) + else if (serverType.contains("Oracle"_L1, Qt::CaseInsensitive)) dbmsType = QSqlDriver::Oracle; - else if (serverType.contains(QLatin1String("MySql"), Qt::CaseInsensitive)) + else if (serverType.contains("MySql"_L1, Qt::CaseInsensitive)) dbmsType = QSqlDriver::MySqlServer; - else if (serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive)) + else if (serverType.contains("Microsoft SQL Server"_L1, Qt::CaseInsensitive)) dbmsType = QSqlDriver::MSSqlServer; - else if (serverType.contains(QLatin1String("Sybase"), Qt::CaseInsensitive)) + else if (serverType.contains("Sybase"_L1, Qt::CaseInsensitive)) dbmsType = QSqlDriver::Sybase; } r = SQLGetInfo(hDbc, @@ -2194,9 +2223,9 @@ void QODBCDriverPrivate::checkDBMS() serverString.data(), SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)), &t); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (SQL_SUCCEEDED(r)) { const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR)); - isFreeTDSDriver = serverType.contains(QLatin1String("tdsodbc"), Qt::CaseInsensitive); + isFreeTDSDriver = serverType.contains("tdsodbc"_L1, Qt::CaseInsensitive); unicode = unicode && !isFreeTDSDriver; } } @@ -2205,46 +2234,42 @@ void QODBCDriverPrivate::checkHasSQLFetchScroll() { SQLUSMALLINT sup; SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup); - if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) { + if ((!SQL_SUCCEEDED(r)) || sup != SQL_TRUE) { hasSQLFetchScroll = false; - qWarning("QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries"); + qSqlWarning("QODBCDriver::checkHasSQLFetchScroll: Driver doesn't support " + "scrollable result sets, use forward only mode for queries"_L1, this); } } void QODBCDriverPrivate::checkHasMultiResults() { - QVarLengthArray<SQLTCHAR> driverResponse(2); + QVarLengthArray<SQLTCHAR, 2> driverResponse(2); SQLSMALLINT length; SQLRETURN r = SQLGetInfo(hDbc, SQL_MULT_RESULT_SETS, driverResponse.data(), SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)), &length); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) - hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(QLatin1Char('Y')); + if (SQL_SUCCEEDED(r)) + hasMultiResultSets = fromSQLTCHAR(driverResponse, length / sizeof(SQLTCHAR)).startsWith(u'Y'); } void QODBCDriverPrivate::checkDateTimePrecision() { SQLINTEGER columnSize; - SQLHANDLE hStmt; + SqlStmtHandle hStmt(hDbc); - SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt); - if (r != SQL_SUCCESS) { + if (!hStmt.isValid()) return; - } - r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - r = SQLFetch(hStmt); - if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) - { - if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) { + SQLRETURN r = SQLGetTypeInfo(hStmt.handle(), SQL_TIMESTAMP); + if (SQL_SUCCEEDED(r)) { + r = SQLFetch(hStmt.handle()); + if (SQL_SUCCEEDED(r)) { + if (SQLGetData(hStmt.handle(), 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) datetimePrecision = (int)columnSize; - } } } - SQLFreeHandle(SQL_HANDLE_STMT, hStmt); } QSqlResult *QODBCDriver::createResult() const @@ -2256,7 +2281,7 @@ bool QODBCDriver::beginTransaction() { Q_D(QODBCDriver); if (!isOpen()) { - qWarning("QODBCDriver::beginTransaction: Database not open"); + qSqlWarning("QODBCDriver::beginTransaction: Database not open"_L1, d); return false; } SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF); @@ -2276,7 +2301,7 @@ bool QODBCDriver::commitTransaction() { Q_D(QODBCDriver); if (!isOpen()) { - qWarning("QODBCDriver::commitTransaction: Database not open"); + qSqlWarning("QODBCDriver::commitTransaction: Database not open"_L1, d); return false; } SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, @@ -2294,7 +2319,7 @@ bool QODBCDriver::rollbackTransaction() { Q_D(QODBCDriver); if (!isOpen()) { - qWarning("QODBCDriver::rollbackTransaction: Database not open"); + qSqlWarning("QODBCDriver::rollbackTransaction: Database not open"_L1, d); return false; } SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, @@ -2329,71 +2354,52 @@ QStringList QODBCDriver::tables(QSql::TableType type) const QStringList tl; if (!isOpen()) return tl; - SQLHANDLE hStmt; - SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, - d->hDbc, - &hStmt); - if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d); + SqlStmtHandle hStmt(d->hDbc); + if (!hStmt.isValid()) { + qSqlWarning("QODBCDriver::tables: Unable to allocate handle"_L1, d); return tl; } - r = SQLSetStmtAttr(hStmt, - SQL_ATTR_CURSOR_TYPE, - (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, - SQL_IS_UINTEGER); + SQLRETURN r = SQLSetStmtAttr(hStmt.handle(), + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); QStringList tableType; if (type & QSql::Tables) - tableType += QLatin1String("TABLE"); + tableType += "TABLE"_L1; if (type & QSql::Views) - tableType += QLatin1String("VIEW"); + tableType += "VIEW"_L1; if (type & QSql::SystemTables) - tableType += QLatin1String("SYSTEM TABLE"); + tableType += "SYSTEM TABLE"_L1; if (tableType.isEmpty()) return tl; - QString joinedTableTypeString = tableType.join(QLatin1Char(',')); + { + auto joinedTableTypeString = toSQLTCHAR(tableType.join(u',')); - r = SQLTables(hStmt, - NULL, - 0, - NULL, - 0, - NULL, - 0, - toSQLTCHAR(joinedTableTypeString).data(), - joinedTableTypeString.length() /* characters, not bytes */); + r = SQLTables(hStmt.handle(), + nullptr, 0, + nullptr, 0, + nullptr, 0, + joinedTableTypeString.data(), joinedTableTypeString.size()); + } if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d); + qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1, + hStmt.handle()); - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); - - if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) { - qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")"; + r = d->sqlFetchNext(hStmt); + if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) { + qSqlWarning("QODBCDriver::tables failed to retrieve table/view list"_L1, + hStmt.handle()); return QStringList(); } while (r == SQL_SUCCESS) { - QString fieldVal = qGetStringData(hStmt, 2, -1, d->unicode); - tl.append(fieldVal); - - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); + tl.append(qGetStringData(hStmt.handle(), 2, -1, d->unicode).toString()); + r = d->sqlFetchNext(hStmt); } - r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); - if (r!= SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); return tl; } @@ -2406,98 +2412,69 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const bool usingSpecialColumns = false; QSqlRecord rec = record(tablename); - SQLHANDLE hStmt; - SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, - d->hDbc, - &hStmt); - if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d); + SqlStmtHandle hStmt(d->hDbc); + if (!hStmt.isValid()) { + qSqlWarning("QODBCDriver::primaryIndex: Unable to allocate handle"_L1, d); return index; } QString catalog, schema, table; - const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table); + d->splitTableQualifier(tablename, catalog, schema, table); - if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) - catalog = stripDelimiters(catalog, QSqlDriver::TableName); - else - catalog = d->adjustCase(catalog); - - if (isIdentifierEscaped(schema, QSqlDriver::TableName)) - schema = stripDelimiters(schema, QSqlDriver::TableName); - else - schema = d->adjustCase(schema); - - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - else - table = d->adjustCase(table); - - r = SQLSetStmtAttr(hStmt, - SQL_ATTR_CURSOR_TYPE, - (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, - SQL_IS_UINTEGER); - r = SQLPrimaryKeys(hStmt, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length() /* in characters, not in bytes */); + SQLRETURN r = SQLSetStmtAttr(hStmt.handle(), + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + { + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLPrimaryKeys(hStmt.handle(), + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size()); + } // if the SQLPrimaryKeys() call does not succeed (e.g the driver // does not support it) - try an alternative method to get hold of // the primary index (e.g MS Access and FoxPro) if (r != SQL_SUCCESS) { - r = SQLSpecialColumns(hStmt, - SQL_BEST_ROWID, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length(), - SQL_SCOPE_CURROW, - SQL_NULLABLE); + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLSpecialColumns(hStmt.handle(), + SQL_BEST_ROWID, + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size(), + SQL_SCOPE_CURROW, + SQL_NULLABLE); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d); + qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1, + hStmt.handle()); } else { usingSpecialColumns = true; } } - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); + r = d->sqlFetchNext(hStmt); int fakeId = 0; QString cName, idxName; // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop while (r == SQL_SUCCESS) { if (usingSpecialColumns) { - cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name + cName = qGetStringData(hStmt.handle(), 1, -1, d->unicode).toString(); // column name idxName = QString::number(fakeId++); // invent a fake index name } else { - cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name - idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name + cName = qGetStringData(hStmt.handle(), 3, -1, d->unicode).toString(); // column name + idxName = qGetStringData(hStmt.handle(), 5, -1, d->unicode).toString(); // pk index name } index.append(rec.field(cName)); index.setName(idxName); - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); - + r = d->sqlFetchNext(hStmt); } - r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); - if (r!= SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); return index; } @@ -2508,72 +2485,39 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const if (!isOpen()) return fil; - SQLHANDLE hStmt; - QString catalog, schema, table; - const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table); - - if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) - catalog = stripDelimiters(catalog, QSqlDriver::TableName); - else - catalog = d->adjustCase(catalog); - - if (isIdentifierEscaped(schema, QSqlDriver::TableName)) - schema = stripDelimiters(schema, QSqlDriver::TableName); - else - schema = d->adjustCase(schema); + SqlStmtHandle hStmt(d->hDbc); + if (!hStmt.isValid()) { + qSqlWarning("QODBCDriver::record: Unable to allocate handle"_L1, d); + return fil; + } - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - else - table = d->adjustCase(table); + QString catalog, schema, table; + d->splitTableQualifier(tablename, catalog, schema, table); - SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, - d->hDbc, - &hStmt); - if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d); - return fil; + SQLRETURN r = SQLSetStmtAttr(hStmt.handle(), + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + { + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLColumns(hStmt.handle(), + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size(), + nullptr, + 0); } - r = SQLSetStmtAttr(hStmt, - SQL_ATTR_CURSOR_TYPE, - (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, - SQL_IS_UINTEGER); - r = SQLColumns(hStmt, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length(), - NULL, - 0); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d); - - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); + qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, hStmt.handle()); + r = d->sqlFetchNext(hStmt); // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop while (r == SQL_SUCCESS) { - - fil.append(qMakeFieldInfo(hStmt, d)); - - if (d->hasSQLFetchScroll) - r = SQLFetchScroll(hStmt, - SQL_FETCH_NEXT, - 0); - else - r = SQLFetch(hStmt); + fil.append(qMakeFieldInfo(hStmt.handle(), d)); + r = d->sqlFetchNext(hStmt); } - - r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); - if (r!= SQL_SUCCESS) - qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d); - return fil; } @@ -2582,33 +2526,33 @@ QString QODBCDriver::formatValue(const QSqlField &field, { QString r; if (field.isNull()) { - r = QLatin1String("NULL"); - } else if (field.metaType().id() == QVariant::DateTime) { + r = "NULL"_L1; + } else if (field.metaType().id() == QMetaType::QDateTime) { // Use an escape sequence for the datetime fields - if (field.value().toDateTime().isValid()){ - QDate dt = field.value().toDateTime().date(); - QTime tm = field.value().toDateTime().time(); + const QDateTime dateTime = field.value().toDateTime(); + if (dateTime.isValid()) { + const QDate dt = dateTime.date(); + const QTime tm = dateTime.time(); // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10 - r = QLatin1String("{ ts '") + - QString::number(dt.year()) + QLatin1Char('-') + - QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) + - QLatin1Char('-') + - QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) + - QLatin1Char(' ') + + r = "{ ts '"_L1 + + QString::number(dt.year()) + u'-' + + QString::number(dt.month()).rightJustified(2, u'0', true) + + u'-' + + QString::number(dt.day()).rightJustified(2, u'0', true) + + u' ' + tm.toString() + - QLatin1String("' }"); + "' }"_L1; } else - r = QLatin1String("NULL"); - } else if (field.metaType().id() == QVariant::ByteArray) { - QByteArray ba = field.value().toByteArray(); - QString res; - static const char hexchars[] = "0123456789abcdef"; - for (int i = 0; i < ba.size(); ++i) { - uchar s = (uchar) ba[i]; - res += QLatin1Char(hexchars[s >> 4]); - res += QLatin1Char(hexchars[s & 0x0f]); + r = "NULL"_L1; + } else if (field.metaType().id() == QMetaType::QByteArray) { + const QByteArray ba = field.value().toByteArray(); + r.reserve((ba.size() + 1) * 2); + r = "0x"_L1; + for (const char c : ba) { + const uchar s = uchar(c); + r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4)); + r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f)); } - r = QLatin1String("0x") + res; } else { r = QSqlDriver::formatValue(field, trimStrings); } @@ -2626,10 +2570,11 @@ QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) Q_D(const QODBCDriver); QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar(); QString res = identifier; - if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) { - res.replace(quote, QString(quote)+QString(quote)); - res.prepend(quote).append(quote); - res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote)); + if (!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) { + const QString quoteStr(quote); + res.replace(quote, quoteStr + quoteStr); + res.replace(u'.', quoteStr + u'.' + quoteStr); + res = quote + res + quote; } return res; } |