diff options
Diffstat (limited to 'src/plugins/sqldrivers')
40 files changed, 4100 insertions, 2513 deletions
diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf index aae2a6822b..10bc1fd407 100644 --- a/src/plugins/sqldrivers/.cmake.conf +++ b/src/plugins/sqldrivers/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.3.0") +set(QT_REPO_MODULE_VERSION "6.8.0") diff --git a/src/plugins/sqldrivers/CMakeLists.txt b/src/plugins/sqldrivers/CMakeLists.txt index 91c971790b..43abb00ad1 100644 --- a/src/plugins/sqldrivers/CMakeLists.txt +++ b/src/plugins/sqldrivers/CMakeLists.txt @@ -1,9 +1,17 @@ -# Generated from sqldrivers.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause -# special case begin cmake_minimum_required(VERSION 3.16) if (NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt") include(.cmake.conf) + # Store initial build type (if any is specified) to be read by + # qt_internal_set_cmake_build_type(). + # See qt_internal_set_cmake_build_type() for details. + if(DEFINED CACHE{CMAKE_BUILD_TYPE}) + set(__qt_internal_standalone_project_cmake_build_type_before_project_call + "${CMAKE_BUILD_TYPE}") + endif() + project(QSQLiteDriverPlugins VERSION "${QT_REPO_MODULE_VERSION}" DESCRIPTION "Qt6 SQL driver plugins" @@ -36,7 +44,7 @@ include(configure.cmake) qt_feature_module_end(NO_MODULE) -if(QT_FEATURE_sql_psql) +if(QT_FEATURE_sql_psql AND QT_FEATURE_regularexpression) add_subdirectory(psql) endif() @@ -48,10 +56,6 @@ if(QT_FEATURE_sql_odbc) add_subdirectory(odbc) endif() -if(QT_FEATURE_sql_tds) -# TODO add_subdirectory(tds) -endif() - if(QT_FEATURE_sql_oci) add_subdirectory(oci) endif() @@ -64,15 +68,14 @@ if(QT_FEATURE_sql_sqlite) add_subdirectory(sqlite) endif() -if(QT_FEATURE_sql_sqlite2) -# TODO add_subdirectory(sqlite2) -endif() - if(QT_FEATURE_sql_ibase) add_subdirectory(ibase) endif() +if(QT_FEATURE_sql_mimer) + add_subdirectory(mimer) +endif() + if(NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt") qt_print_feature_summary() endif() -# special case end diff --git a/src/plugins/sqldrivers/configure.cmake b/src/plugins/sqldrivers/configure.cmake index 25384f3c52..534ac020d8 100644 --- a/src/plugins/sqldrivers/configure.cmake +++ b/src/plugins/sqldrivers/configure.cmake @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + #### Inputs @@ -17,6 +20,7 @@ qt_find_package(Oracle PROVIDED_TARGETS Oracle::OCI MODULE_NAME sqldrivers QMAKE qt_find_package(ODBC PROVIDED_TARGETS ODBC::ODBC MODULE_NAME sqldrivers QMAKE_LIB odbc) qt_find_package(SQLite3 PROVIDED_TARGETS SQLite::SQLite3 MODULE_NAME sqldrivers QMAKE_LIB sqlite3) qt_find_package(Interbase PROVIDED_TARGETS Interbase::Interbase MODULE_NAME sqldrivers QMAKE_LIB ibase) # special case +qt_find_package(Mimer PROVIDED_TARGETS MimerSQL::MimerSQL MODULE_NAME sqldrivers QMAKE_LIB mimer) if(NOT WIN32 AND QT_FEATURE_system_zlib) qt_add_qmake_lib_dependency(sqlite3 zlib) endif() @@ -61,6 +65,11 @@ qt_feature("system-sqlite" PRIVATE AUTODETECT OFF CONDITION QT_FEATURE_sql_sqlite AND SQLite3_FOUND ) +qt_feature("sql-mimer" PRIVATE + LABEL "Mimer" + CONDITION Mimer_FOUND +) + qt_configure_add_summary_section(NAME "Qt Sql Drivers") qt_configure_add_summary_entry(ARGS "sql-db2") qt_configure_add_summary_entry(ARGS "sql-ibase") @@ -70,6 +79,7 @@ qt_configure_add_summary_entry(ARGS "sql-odbc") qt_configure_add_summary_entry(ARGS "sql-psql") qt_configure_add_summary_entry(ARGS "sql-sqlite") qt_configure_add_summary_entry(ARGS "system-sqlite") +qt_configure_add_summary_entry(ARGS "sql-mimer") qt_configure_end_summary_section() # end of "Qt Sql Drivers" section qt_configure_add_report_entry( TYPE WARNING diff --git a/src/plugins/sqldrivers/db2/CMakeLists.txt b/src/plugins/sqldrivers/db2/CMakeLists.txt index 1c693faa3b..119a5b53c9 100644 --- a/src/plugins/sqldrivers/db2/CMakeLists.txt +++ b/src/plugins/sqldrivers/db2/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from db2.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QDB2DriverPlugin Plugin: @@ -20,9 +21,6 @@ qt_internal_add_plugin(QDB2DriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:db2.pro:<TRUE>: -# OTHER_FILES = "db2.json" - ## Scopes: ##################################################################### diff --git a/src/plugins/sqldrivers/db2/main.cpp b/src/plugins/sqldrivers/db2/main.cpp index 97338b8eef..b5aa5a6492 100644 --- a/src/plugins/sqldrivers/db2/main.cpp +++ b/src/plugins/sqldrivers/db2/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QDB2DriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,7 +27,7 @@ QDB2DriverPlugin::QDB2DriverPlugin() QSqlDriver* QDB2DriverPlugin::create(const QString &name) { - if (name == QLatin1String("QDB2")) { + if (name == "QDB2"_L1) { QDB2Driver* driver = new QDB2Driver(); return driver; } diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp index e819d0ea86..9b6a06c378 100644 --- a/src/plugins/sqldrivers/db2/qsql_db2.cpp +++ b/src/plugins/sqldrivers/db2/qsql_db2.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_db2_p.h" #include <qcoreapplication.h> @@ -50,6 +14,7 @@ #include <QDebug> #include <QtSql/private/qsqldriver_p.h> #include <QtSql/private/qsqlresult_p.h> +#include "private/qtools_p.h" #if defined(Q_CC_BOR) // DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE @@ -65,6 +30,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static const int COLNAMESIZE = 255; // Based on what is mentioned in the documentation here: // https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/sqlref/src/tpc/db2z_limits.html @@ -77,10 +44,13 @@ class QDB2DriverPrivate : public QSqlDriverPrivate Q_DECLARE_PUBLIC(QDB2Driver) public: - QDB2DriverPrivate() : QSqlDriverPrivate(), hEnv(0), hDbc(0) { dbmsType = QSqlDriver::DB2; } - SQLHANDLE hEnv; - SQLHANDLE hDbc; + QDB2DriverPrivate() : QSqlDriverPrivate(QSqlDriver::DB2) {} + SQLHANDLE hEnv = 0; + SQLHANDLE hDbc = 0; QString user; + + void qSplitTableQualifier(const QString &qualifier, QString &catalog, + QString &schema, QString &table) const; }; class QDB2ResultPrivate; @@ -188,7 +158,7 @@ static QString qDB2Warn(const QDB2DriverPrivate* d, QStringList *errorCodes = nu errorCode = 0; } if (!error.isEmpty()) - error += QLatin1Char(' '); + error += u' '; error += qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc, &errorCode); if (errorCodes && errorCode != 0) *errorCodes << QString::number(errorCode); @@ -204,14 +174,14 @@ static QString qDB2Warn(const QDB2ResultPrivate* d, QStringList *errorCodes = nu errorCode = 0; } if (!error.isEmpty()) - error += QLatin1Char(' '); + error += u' '; error += qWarnDB2Handle(SQL_HANDLE_DBC, d->drv_d_func()->hDbc, &errorCode); if (errorCodes && errorCode != 0) { *errorCodes << QString::number(errorCode); errorCode = 0; } if (!error.isEmpty()) - error += QLatin1Char(' '); + error += u' '; error += qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt, &errorCode); if (errorCodes && errorCode != 0) *errorCodes << QString::number(errorCode); @@ -236,7 +206,7 @@ static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, QStringList errorCodes; const QString error = qDB2Warn(p, &errorCodes); return QSqlError(QStringLiteral("QDB2: ") + err, error, type, - errorCodes.join(QLatin1Char(';'))); + errorCodes.join(u';')); } static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, @@ -245,7 +215,7 @@ static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, QStringList errorCodes; const QString error = qDB2Warn(p, &errorCodes); return QSqlError(QStringLiteral("QDB2: ") + err, error, type, - errorCodes.join(QLatin1Char(';'))); + errorCodes.join(u';')); } static QMetaType qDecodeDB2Type(SQLSMALLINT sqltype) @@ -335,7 +305,6 @@ static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i) // else required is unknown f.setLength(colSize == 0 ? -1 : int(colSize)); f.setPrecision(colScale == 0 ? -1 : int(colScale)); - f.setSqlType(int(colType)); SQLTCHAR tableName[TABLENAMESIZE]; SQLSMALLINT tableNameLen; r = SQLColAttribute(d->hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName, @@ -495,34 +464,27 @@ static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLLEN& lengthIndi return fieldVal; } -static void qSplitTableQualifier(const QString & qualifier, QString * catalog, - QString * schema, QString * table) +void QDB2DriverPrivate::qSplitTableQualifier(const QString &qualifier, QString &catalog, + QString &schema, QString &table) const { - if (!catalog || !schema || !table) - 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 = qualifier; + break; + case 2: + schema = l.at(0).toString(); + table = l.at(1).toString(); + break; + case 3: + catalog = l.at(0).toString(); + schema = l.at(1).toString(); + table = l.at(2).toString(); + break; + default: + qSqlWarning(QString::fromLatin1("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'") + .arg(qualifier), this); + break; } } @@ -542,7 +504,6 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt) // else we don't know. f.setLength(qGetIntData(hStmt, 6, isNull)); // column size f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision - f.setSqlType(type); return f; } @@ -554,13 +515,13 @@ static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwa d->drv_d_func()->hDbc, &d->hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d); + qSqlWarning("QDB2Result::reset: Unable to allocate statement handle"_L1, d); return false; } } else { r = SQLFreeStmt(d->hStmt, SQL_CLOSE); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d); + qSqlWarning("QDB2Result::reset: Unable to close statement handle"_L1, d); return false; } } @@ -581,8 +542,8 @@ static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwa } if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg( - forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY") - : QLatin1String("SQL_CURSOR_STATIC")), d); + forwardOnly ? "SQL_CURSOR_FORWARD_ONLY"_L1 + : "SQL_CURSOR_STATIC"_L1), d); return false; } return true; @@ -607,8 +568,7 @@ QDB2Result::~QDB2Result() if (d->hStmt) { SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") - + QString::number(r), d); + qSqlWarning("QDB2Driver: Unable to free statement handle "_L1 + QString::number(r), d); } } @@ -697,7 +657,7 @@ bool QDB2Result::exec() for (i = 0; i < values.count(); ++i) { // bind parameters - only positional binding allowed SQLLEN *ind = &indicators[i]; - if (values.at(i).isNull()) + if (QSqlResultPrivate::isVariantNull(values.at(i))) *ind = SQL_NULL_DATA; if (bindValueType(i) & QSql::Out) values[i].detach(); @@ -808,11 +768,11 @@ bool QDB2Result::exec() break; } case QMetaType::QString: { - QString str(values.at(i).toString()); + const QString str(values.at(i).toString()); if (*ind != SQL_NULL_DATA) *ind = str.length() * sizeof(QChar); if (bindValueType(i) & QSql::Out) { - QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar)); + QByteArray ba((char *)str.data(), str.capacity() * sizeof(QChar)); r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & 3], @@ -910,7 +870,7 @@ bool QDB2Result::exec() break; } case QMetaType::Int: case QMetaType::Double: - case QMetaType::ByteArray: + case QMetaType::QByteArray: break; case QMetaType::QString: if (bindValueType(i) & QSql::Out) @@ -1159,7 +1119,7 @@ int QDB2Result::numRowsAffected() if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) return affectedRowCount; else - qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d); + qSqlWarning("QDB2Result::numRowsAffected: Unable to count affected rows"_L1, d); return -1; } @@ -1254,7 +1214,7 @@ bool QDB2Driver::open(const QString& db, const QString& user, const QString& pas SQL_NULL_HANDLE, &d->hEnv); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate environment"), d); + qSqlWarning("QDB2Driver::open: Unable to allocate environment"_L1, d); setOpenError(true); return false; } @@ -1263,18 +1223,18 @@ bool QDB2Driver::open(const QString& db, const QString& user, const QString& pas d->hEnv, &d->hDbc); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate connection"), d); + qSqlWarning("QDB2Driver::open: Unable to allocate connection"_L1, d); setOpenError(true); return false; } QString protocol; // Set connection attributes - const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts)); + const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts)); for (int i = 0; i < opts.count(); ++i) { const QString tmp(opts.at(i)); int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { + if ((idx = tmp.indexOf(u'=')) == -1) { qWarning("QDB2Driver::open: Illegal connect option value '%s'", tmp.toLocal8Bit().constData()); continue; @@ -1285,10 +1245,10 @@ bool QDB2Driver::open(const QString& db, const QString& user, const QString& pas SQLULEN v = 0; r = SQL_SUCCESS; - if (opt == QLatin1String("SQL_ATTR_ACCESS_MODE")) { - if (val == 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 == QLatin1String("SQL_MODE_READ_WRITE")) { + } else if (val == "SQL_MODE_READ_WRITE"_L1) { v = SQL_MODE_READ_WRITE; } else { qWarning("QDB2Driver::open: Unknown option value '%s'", @@ -1296,10 +1256,10 @@ bool QDB2Driver::open(const QString& db, const QString& user, const QString& pas continue; } r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_ACCESS_MODE, reinterpret_cast<SQLPOINTER>(v), 0); - } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { + } else if (opt == "SQL_ATTR_LOGIN_TIMEOUT"_L1) { v = val.toUInt(); r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, reinterpret_cast<SQLPOINTER>(v), 0); - } else if (opt.compare(QLatin1String("PROTOCOL"), Qt::CaseInsensitive) == 0) { + } else if (opt.compare("PROTOCOL"_L1, Qt::CaseInsensitive) == 0) { protocol = tmp; } else { @@ -1312,15 +1272,15 @@ bool QDB2Driver::open(const QString& db, const QString& user, const QString& pas } if (protocol.isEmpty()) - protocol = QLatin1String("PROTOCOL=TCPIP"); + protocol = "PROTOCOL=TCPIP"_L1; if (port < 0 ) port = 50000; QString connQStr; - connQStr = protocol + QLatin1String(";DATABASE=") + db + QLatin1String(";HOSTNAME=") + host - + QLatin1String(";PORT=") + QString::number(port) + QLatin1String(";UID=") + user - + QLatin1String(";PWD=") + password; + connQStr = protocol + ";DATABASE="_L1 + db + ";HOSTNAME="_L1 + host + + ";PORT="_L1 + QString::number(port) + ";UID="_L1 + user + + ";PWD="_L1 + password; SQLTCHAR connOut[SQL_MAX_OPTION_STRING_LENGTH]; @@ -1356,18 +1316,18 @@ void QDB2Driver::close() if (isOpen()) { r = SQLDisconnect(d->hDbc); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver::close: Unable to disconnect datasource"), d); + qSqlWarning("QDB2Driver::close: Unable to disconnect datasource"_L1, d); } r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver::close: Unable to free connection handle"), d); + qSqlWarning("QDB2Driver::close: 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("QDB2Driver::close: Unable to free environment handle"), d); + qSqlWarning("QDB2Driver::close: Unable to free environment handle"_L1, d); d->hEnv = 0; } setOpen(false); @@ -1388,7 +1348,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const SQLHANDLE hStmt; QString catalog, schema, table; - qSplitTableQualifier(tableName, &catalog, &schema, &table); + d->qSplitTableQualifier(tableName, catalog, schema, table); if (schema.isEmpty()) schema = d->user; @@ -1411,7 +1371,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const d->hDbc, &hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QDB2Driver::record: Unable to allocate handle"), d); + qSqlWarning("QDB2Driver::record: Unable to allocate handle"_L1, d); return fil; } @@ -1434,7 +1394,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const 0); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver::record: Unable to execute column list"), d); + qSqlWarning("QDB2Driver::record: Unable to execute column list"_L1, d); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); @@ -1449,7 +1409,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + qSqlWarning("QDB2Driver: Unable to free statement handle "_L1 + QString::number(r), d); return fil; @@ -1468,7 +1428,7 @@ QStringList QDB2Driver::tables(QSql::TableType type) const d->hDbc, &hStmt); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { - qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to allocate handle"), d); + qSqlWarning("QDB2Driver::tables: Unable to allocate handle"_L1, d); return tl; } r = SQLSetStmtAttr(hStmt, @@ -1478,11 +1438,11 @@ QStringList QDB2Driver::tables(QSql::TableType type) const QString 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; tableType.chop(1); @@ -1498,7 +1458,7 @@ QStringList QDB2Driver::tables(QSql::TableType type) const tableType.length()); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) - qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to execute table list"), d); + qSqlWarning("QDB2Driver::tables: Unable to execute table list"_L1, d); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); @@ -1513,7 +1473,7 @@ QStringList QDB2Driver::tables(QSql::TableType type) const user = user.toUpper(); if (userVal != user) - fieldVal = userVal + QLatin1Char('.') + fieldVal; + fieldVal = userVal + u'.' + fieldVal; tl.append(fieldVal); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, @@ -1522,7 +1482,7 @@ QStringList QDB2Driver::tables(QSql::TableType type) const r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r != SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to free statement handle ") + qSqlWarning("QDB2Driver::tables: Unable to free statement handle "_L1 + QString::number(r), d); return tl; } @@ -1540,11 +1500,11 @@ QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const d->hDbc, &hStmt); if (r != SQL_SUCCESS) { - qSqlWarning(QLatin1String("QDB2Driver::primaryIndex: Unable to list primary key"), d); + qSqlWarning("QDB2Driver::primaryIndex: Unable to list primary key"_L1, d); return index; } QString catalog, schema, table; - qSplitTableQualifier(tablename, &catalog, &schema, &table); + d->qSplitTableQualifier(tablename, catalog, schema, table); if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) catalog = stripDelimiters(catalog, QSqlDriver::TableName); @@ -1591,7 +1551,7 @@ QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const } r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r!= SQL_SUCCESS) - qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + qSqlWarning("QDB2Driver: Unable to free statement handle "_L1 + QString::number(r), d); return index; } @@ -1685,7 +1645,7 @@ bool QDB2Driver::setAutoCommit(bool autoCommit) QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const { if (field.isNull()) - return QLatin1String("NULL"); + return "NULL"_L1; switch (field.metaType().id()) { case QMetaType::QDateTime: { @@ -1694,32 +1654,32 @@ QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const QDate dt = field.value().toDateTime().date(); QTime tm = field.value().toDateTime().time(); // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10 - return QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-') - + QString::number(dt.month()) + QLatin1Char('-') - + QString::number(dt.day()) + QLatin1Char('-') - + QString::number(tm.hour()) + QLatin1Char('.') - + QString::number(tm.minute()).rightJustified(2, QLatin1Char('0'), true) - + QLatin1Char('.') - + QString::number(tm.second()).rightJustified(2, QLatin1Char('0'), true) - + QLatin1Char('.') - + QString::number(tm.msec() * 1000).rightJustified(6, QLatin1Char('0'), true) - + QLatin1Char('\''); + return u'\'' + QString::number(dt.year()) + u'-' + + QString::number(dt.month()) + u'-' + + QString::number(dt.day()) + u'-' + + QString::number(tm.hour()) + u'.' + + QString::number(tm.minute()).rightJustified(2, u'0', true) + + u'.' + + QString::number(tm.second()).rightJustified(2, u'0', true) + + u'.' + + QString::number(tm.msec() * 1000).rightJustified(6, u'0', true) + + u'\''; } else { - return QLatin1String("NULL"); + return "NULL"_L1; } } case QMetaType::QByteArray: { - QByteArray ba = field.value().toByteArray(); - QString res; - res += QLatin1String("BLOB(X'"); - 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]); + const QByteArray ba = field.value().toByteArray(); + QString r; + r.reserve(ba.size() * 2 + 9); + r += "BLOB(X'"_L1; + for (const char c : ba) { + const uchar s = uchar(c); + r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4)); + r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f)); } - res += QLatin1String("')"); - return res; + r += "')"_L1; + return r; } default: return QSqlDriver::formatValue(field, trimStrings); @@ -1735,12 +1695,14 @@ QVariant QDB2Driver::handle() const QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) { + res.replace(u'"', "\"\""_L1); + res.replace(u'.', "\".\""_L1); + res = u'"' + res + u'"'; } return res; } QT_END_NAMESPACE + +#include "moc_qsql_db2_p.cpp" diff --git a/src/plugins/sqldrivers/db2/qsql_db2_p.h b/src/plugins/sqldrivers/db2/qsql_db2_p.h index abe65c4208..9e580352b2 100644 --- a/src/plugins/sqldrivers/db2/qsql_db2_p.h +++ b/src/plugins/sqldrivers/db2/qsql_db2_p.h @@ -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 #ifndef QSQL_DB2_H #define QSQL_DB2_H diff --git a/src/plugins/sqldrivers/ibase/CMakeLists.txt b/src/plugins/sqldrivers/ibase/CMakeLists.txt index 8cd5c24dfc..b8f2d2561f 100644 --- a/src/plugins/sqldrivers/ibase/CMakeLists.txt +++ b/src/plugins/sqldrivers/ibase/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_plugin(QIBaseDriverPlugin OUTPUT_NAME qsqlibase PLUGIN_TYPE sqldrivers @@ -7,6 +10,7 @@ qt_internal_add_plugin(QIBaseDriverPlugin DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES Interbase::Interbase Qt::Core diff --git a/src/plugins/sqldrivers/ibase/main.cpp b/src/plugins/sqldrivers/ibase/main.cpp index 9f34899d8c..24e9dc0864 100644 --- a/src/plugins/sqldrivers/ibase/main.cpp +++ b/src/plugins/sqldrivers/ibase/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QIBaseDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,7 +27,7 @@ QIBaseDriverPlugin::QIBaseDriverPlugin() QSqlDriver* QIBaseDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QIBASE")) { + if (name == "QIBASE"_L1) { QIBaseDriver* driver = new QIBaseDriver(); return driver; } diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp index ba820a4416..f6eed5e227 100644 --- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp +++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp @@ -1,63 +1,35 @@ -/**************************************************************************** -** -** 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) 2022 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_ibase_p.h" -#include <qcoreapplication.h> -#include <qdatetime.h> -#include <qdeadlinetimer.h> -#include <qdebug.h> -#include <qlist.h> -#include <qmutex.h> -#include <qsqlerror.h> -#include <qsqlfield.h> -#include <qsqlindex.h> -#include <qsqlquery.h> -#include <qvariant.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qtimezone.h> +#include <QtCore/qdeadlinetimer.h> +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qmap.h> +#include <QtCore/qmutex.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvarlengtharray.h> +#include <QtSql/qsqlerror.h> +#include <QtSql/qsqlfield.h> +#include <QtSql/qsqlindex.h> +#include <QtSql/qsqlquery.h> #include <QtSql/private/qsqlcachedresult_p.h> #include <QtSql/private/qsqldriver_p.h> #include <stdlib.h> #include <limits.h> #include <math.h> -#include <QVarLengthArray> +#include <mutex> QT_BEGIN_NAMESPACE +static Q_LOGGING_CATEGORY(lcIbase, "qt.sql.ibase") + +using namespace Qt::StringLiterals; + #define FBVERSION SQL_DIALECT_V6 #ifndef SQLDA_CURRENT_VERSION @@ -69,7 +41,15 @@ QT_BEGIN_NAMESPACE #define blr_boolean_dtype blr_bool #endif -enum { QIBaseChunkSize = SHRT_MAX / 2 }; +constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2; + +#if (FB_API_VER >= 40) +typedef QMap<quint16, QByteArray> QFbTzIdToIanaIdMap; +typedef QMap<QByteArray, quint16> QIanaIdToFbTzIdMap; +Q_GLOBAL_STATIC(QFbTzIdToIanaIdMap, qFbTzIdToIanaIdMap) +Q_GLOBAL_STATIC(QIanaIdToFbTzIdMap, qIanaIdToFbTzIdMap) +std::once_flag initTZMappingFlag; +#endif static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode) { @@ -81,7 +61,7 @@ static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlc char buf[512]; while(fb_interpret(buf, 512, &status)) { if (!msg.isEmpty()) - msg += QLatin1String(" - "); + msg += " - "_L1; msg += QString::fromUtf8(buf); } return true; @@ -118,6 +98,9 @@ static void initDA(XSQLDA *sqlda) case SQL_FLOAT: case SQL_DOUBLE: case SQL_TIMESTAMP: +#if (FB_API_VER >= 40) + case SQL_TIMESTAMP_TZ: +#endif case SQL_TYPE_TIME: case SQL_TYPE_DATE: case SQL_TEXT: @@ -135,6 +118,7 @@ static void initDA(XSQLDA *sqlda) default: // not supported - do not bind. sqlda->sqlvar[i].sqldata = 0; + qCWarning(lcIbase, "initDA: unknown sqltype: %d", sqlda->sqlvar[i].sqltype & ~1); break; } if (sqlda->sqlvar[i].sqltype & 1) { @@ -155,7 +139,7 @@ static void delDA(XSQLDA *&sqlda) delete [] sqlda->sqlvar[i].sqldata; } free(sqlda); - sqlda = 0; + sqlda = nullptr; } static QMetaType::Type qIBaseTypeName(int iType, bool hasScale) @@ -172,6 +156,9 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale) case blr_sql_date: return QMetaType::QDate; case blr_timestamp: +#if (FB_API_VER >= 40) + case blr_timestamp_tz: +#endif return QMetaType::QDateTime; case blr_blob: return QMetaType::QByteArray; @@ -188,7 +175,7 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale) case blr_boolean_dtype: return QMetaType::Bool; } - qWarning("qIBaseTypeName: unknown datatype: %d", iType); + qCWarning(lcIbase, "qIBaseTypeName: unknown datatype: %d", iType); return QMetaType::UnknownType; } @@ -207,6 +194,9 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale) case SQL_DOUBLE: return QMetaType::Double; case SQL_TIMESTAMP: +#if (FB_API_VER >= 40) + case SQL_TIMESTAMP_TZ: +#endif return QMetaType::QDateTime; case SQL_TYPE_TIME: return QMetaType::QTime; @@ -219,8 +209,10 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale) case SQL_BOOLEAN: return QMetaType::Bool; default: - return QMetaType::UnknownType; + break; } + qCWarning(lcIbase, "qIBaseTypeName: unknown datatype: %d", iType); + return QMetaType::UnknownType; } static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) @@ -241,12 +233,45 @@ static QDateTime fromTimeStamp(char *buffer) // have to demangle the structure ourselves because isc_decode_time // strips the msecs - t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10)); - d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); - + auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer); + t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10)); + d = bd.addDays(timebuf->timestamp_date); return QDateTime(d, t); } +#if (FB_API_VER >= 40) +QDateTime fromTimeStampTz(char *buffer) +{ + static const QDate bd(1858, 11, 17); + QTime t(0, 0); + QDate d; + + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer); + t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10)); + d = bd.addDays(timebuf->utc_timestamp.timestamp_date); + quint16 fpTzID = timebuf->time_zone; + + QByteArray timeZoneName = qFbTzIdToIanaIdMap()->value(fpTzID); + if (!timeZoneName.isEmpty()) + return QDateTime(d, t, QTimeZone(timeZoneName)); + else + return {}; +} + +ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt) +{ + static const QTime midnight(0, 0, 0, 0); + static const QDate basedate(1858, 11, 17); + ISC_TIMESTAMP_TZ ts; + ts.utc_timestamp.timestamp_time = midnight.msecsTo(dt.time()) * 10; + ts.utc_timestamp.timestamp_date = basedate.daysTo(dt.date()); + ts.time_zone = qIanaIdToFbTzIdMap()->value(dt.timeZone().id().simplified(), 0); + return ts; +} +#endif + static ISC_TIME toTime(QTime t) { static const QTime midnight(0, 0, 0, 0); @@ -315,6 +340,34 @@ public: return true; } +#if (FB_API_VER >= 40) + void initTZMappingCache() + { + Q_Q(QIBaseDriver); + QSqlQuery qry(q->createResult()); + qry.setForwardOnly(true); + qry.exec(QString("select * from RDB$TIME_ZONES"_L1)); + if (qry.lastError().type()) { + q->setLastError(QSqlError( + QCoreApplication::translate("QIBaseDriver", + "failed to query time zone mapping from system table"), + qry.lastError().databaseText(), + QSqlError::StatementError, + qry.lastError().nativeErrorCode())); + + return; + } + + while (qry.next()) { + auto record = qry.record(); + quint16 fbTzId = record.value(0).value<quint16>(); + QByteArray ianaId = record.value(1).toByteArray().simplified(); + qFbTzIdToIanaIdMap()->insert(fbTzId, ianaId); + qIanaIdToFbTzIdMap()->insert(ianaId, fbTzId); + } + } +#endif + public: isc_db_handle ibase; isc_tr_handle trans; @@ -353,6 +406,42 @@ protected: int size() override; int numRowsAffected() override; QSqlRecord record() const override; + + template<typename T> + QVariant applyScale(T val, int scale) const + { + if (scale >= 0) + return QVariant(val); + + switch (numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + return QVariant(qint32(val * pow(10.0, scale))); + case QSql::LowPrecisionInt64: + return QVariant(qint64(val * pow(10.0, scale))); + case QSql::LowPrecisionDouble: + return QVariant(double(val * pow(10.0, scale))); + case QSql::HighPrecision: { + const bool negative = val < 0; + QString number; + if constexpr (std::is_signed_v<T> || negative) + number = QString::number(qAbs(val)); + else + number = QString::number(val); + auto len = number.size(); + scale *= -1; + if (scale >= len) { + number = QString(scale - len + 1, u'0') + number; + len = number.size(); + } + const auto sepPos = len - scale; + number = number.left(sepPos) + u'.' + number.mid(sepPos); + if (negative) + number = u'-' + number; + return QVariant(number); + } + } + return QVariant(val); + } }; class QIBaseResultPrivate: public QSqlCachedResultPrivate @@ -385,9 +474,9 @@ public: bool isSelect(); QVariant fetchBlob(ISC_QUAD *bId); - bool writeBlob(int i, const QByteArray &ba); + bool writeBlob(qsizetype iPos, const QByteArray &ba); QVariant fetchArray(int pos, ISC_QUAD *arr); - bool writeArray(int i, const QList<QVariant> &list); + bool writeArray(qsizetype i, const QList<QVariant> &list); public: ISC_STATUS status[20]; isc_tr_handle trans; @@ -407,8 +496,8 @@ QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *dr localTransaction(!drv_d_func()->ibase), stmt(0), ibase(drv_d_func()->ibase), - sqlda(0), - inda(0), + sqlda(nullptr), + inda(nullptr), queryType(-1) { } @@ -432,20 +521,20 @@ void QIBaseResultPrivate::cleanup() q->cleanup(); } -bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba) +bool QIBaseResultPrivate::writeBlob(qsizetype iPos, const QByteArray &ba) { isc_blob_handle handle = 0; - ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata; + ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[iPos].sqldata; isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0); if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"), QSqlError::StatementError)) { - int i = 0; + qsizetype i = 0; while (i < ba.size()) { - isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)), - const_cast<char*>(ba.data()) + i); + isc_put_segment(status, &handle, qMin(ba.size() - i, QIBaseChunkSize), + ba.data() + i); if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB"))) return false; - i += qMin(ba.size() - i, int(QIBaseChunkSize)); + i += qMin(ba.size() - i, QIBaseChunkSize); } } isc_close_blob(status, &handle); @@ -465,7 +554,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) QByteArray ba; int chunkSize = QIBaseChunkSize; ba.resize(chunkSize); - int read = 0; + qsizetype read = 0; while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) { read += len; ba.resize(read + chunkSize); @@ -487,7 +576,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) } template<typename T> -static QList<QVariant> toList(char** buf, int count, T* = nullptr) +static QList<QVariant> toList(char** buf, int count) { QList<QVariant> res; for (int i = 0; i < count; ++i) { @@ -526,7 +615,7 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, } break; } case blr_long: - valList = toList<int>(&buffer, numElements[dim], static_cast<int *>(0)); + valList = toList<int>(&buffer, numElements[dim]); break; case blr_short: valList = toList<short>(&buffer, numElements[dim]); @@ -546,8 +635,16 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, buffer += sizeof(ISC_TIMESTAMP); } break; +#if (FB_API_VER >= 40) + case blr_timestamp_tz: + for (int i = 0; i < numElements[dim]; ++i) { + valList.append(fromTimeStampTz(buffer)); + buffer += sizeof(ISC_TIMESTAMP_TZ); + } + break; +#endif case blr_sql_time: - for(int i = 0; i < numElements[dim]; ++i) { + for (int i = 0; i < numElements[dim]; ++i) { valList.append(fromTime(buffer)); buffer += sizeof(ISC_TIME); } @@ -626,9 +723,8 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) template<typename T> static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr) { - for (int i = 0; i < list.size(); ++i) { - T val; - val = qvariant_cast<T>(list.at(i)); + for (const auto &elem : list) { + T val = qvariant_cast<T>(elem); memcpy(buffer, &val, sizeof(T)); buffer += sizeof(T); } @@ -638,11 +734,9 @@ static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr) template<> char* fillList<float>(char *buffer, const QList<QVariant> &list, float*) { - for (int i = 0; i < list.size(); ++i) { - double val; - float val2 = 0; - val = qvariant_cast<double>(list.at(i)); - val2 = (float)val; + for (const auto &elem : list) { + double val = qvariant_cast<double>(elem); + float val2 = (float)val; memcpy(buffer, &val2, sizeof(float)); buffer += sizeof(float); } @@ -677,7 +771,6 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, QMetaType::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc, QString& error) { - int i; ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds; short dim = arrayDesc->array_desc_dimensions - 1; @@ -685,21 +778,21 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, bounds[curDim].array_bound_lower + 1); if (list.size() != elements) { // size mismatch - error = QLatin1String("Expected size: %1. Supplied size: %2"); - error = QLatin1String("Array size mismatch. Fieldname: %1 ") + error = "Expected size: %1. Supplied size: %2"_L1; + error = "Array size mismatch. Fieldname: %1 "_L1 + error.arg(elements).arg(list.size()); return 0; } if (curDim != dim) { - for(i = 0; i < list.size(); ++i) { + for (const auto &elem : list) { - if (list.at(i).typeId() != QMetaType::QVariantList) { // dimensions mismatch - error = QLatin1String("Array dimensons mismatch. Fieldname: %1"); + if (elem.typeId() != QMetaType::QVariantList) { // dimensions mismatch + error = "Array dimensons mismatch. Fieldname: %1"_L1; return 0; } - buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1, + buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1, arrayDesc, error); if (!buffer) return 0; @@ -726,29 +819,40 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, buffer = fillList<quint64>(buffer, list); break; case QMetaType::QString: - for (i = 0; i < list.size(); ++i) - buffer = qFillBufferWithString(buffer, list.at(i).toString(), + for (const auto &elem : list) + buffer = qFillBufferWithString(buffer, elem.toString(), arrayDesc->array_desc_length, arrayDesc->array_desc_dtype == blr_varying, true); break; case QMetaType::QDate: - for (i = 0; i < list.size(); ++i) { - *((ISC_DATE*)buffer) = toDate(list.at(i).toDate()); + for (const auto &elem : list) { + *((ISC_DATE*)buffer) = toDate(elem.toDate()); buffer += sizeof(ISC_DATE); } break; case QMetaType::QTime: - for (i = 0; i < list.size(); ++i) { - *((ISC_TIME*)buffer) = toTime(list.at(i).toTime()); + for (const auto &elem : list) { + *((ISC_TIME*)buffer) = toTime(elem.toTime()); buffer += sizeof(ISC_TIME); } break; - case QMetaType::QDateTime: - for (i = 0; i < list.size(); ++i) { - *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime()); - buffer += sizeof(ISC_TIMESTAMP); + for (const auto &elem : list) { + switch (arrayDesc->array_desc_dtype) { + case blr_timestamp: + *((ISC_TIMESTAMP*)buffer) = toTimeStamp(elem.toDateTime()); + buffer += sizeof(ISC_TIMESTAMP); + break; +#if (FB_API_VER >= 40) + case blr_timestamp_tz: + *((ISC_TIMESTAMP_TZ*)buffer) = toTimeStampTz(elem.toDateTime()); + buffer += sizeof(ISC_TIMESTAMP_TZ); + break; +#endif + default: + break; + } } break; case QMetaType::Bool: @@ -761,7 +865,7 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, return buffer; } -bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) +bool QIBaseResultPrivate::writeArray(qsizetype column, const QList<QVariant> &list) { Q_Q(QIBaseResult); QString error; @@ -778,7 +882,6 @@ bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) short arraySize = 1; ISC_LONG bufLen; - QList<QVariant> subList = list; short dimensions = desc.array_desc_dimensions; for(int i = 0; i < dimensions; ++i) { @@ -798,16 +901,16 @@ bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) ba.resize(int(bufLen)); if (list.size() > arraySize) { - error = QLatin1String("Array size mismatch: size of %1 is %2, size of provided list is %3"); - error = error.arg(QLatin1String(sqlname)).arg(arraySize).arg(list.size()); - q->setLastError(QSqlError(error, QLatin1String(""), QSqlError::StatementError)); + error = "Array size mismatch: size of %1 is %2, size of provided list is %3"_L1; + error = error.arg(QLatin1StringView(sqlname)).arg(arraySize).arg(list.size()); + q->setLastError(QSqlError(error, ""_L1, QSqlError::StatementError)); return false; } if (!createArrayBuffer(ba.data(), list, qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0), 0, &desc, error)) { - q->setLastError(QSqlError(error.arg(QLatin1String(sqlname)), QLatin1String(""), + q->setLastError(QSqlError(error.arg(QLatin1StringView(sqlname)), ""_L1, QSqlError::StatementError)); return false; } @@ -880,7 +983,7 @@ QIBaseResult::QIBaseResult(const QIBaseDriver *db) bool QIBaseResult::prepare(const QString& query) { Q_D(QIBaseResult); -// qDebug("prepare: %s", qPrintable(query)); +// qDebug("prepare: %ls", qUtf16Printable(query)); if (!driver() || !driver()->isOpen() || driver()->isOpenError()) return false; d->cleanup(); @@ -889,13 +992,13 @@ bool QIBaseResult::prepare(const QString& query) createDA(d->sqlda); if (d->sqlda == (XSQLDA*)0) { - qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; + qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory"; return false; } createDA(d->inda); if (d->inda == (XSQLDA*)0){ - qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; + qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory"; return false; } @@ -919,7 +1022,7 @@ bool QIBaseResult::prepare(const QString& query) if (d->inda->sqld > d->inda->sqln) { enlargeDA(d->inda, d->inda->sqld); if (d->inda == (XSQLDA*)0) { - qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory"; + qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory"; return false; } @@ -933,7 +1036,7 @@ bool QIBaseResult::prepare(const QString& query) // need more field descriptors enlargeDA(d->sqlda, d->sqlda->sqld); if (d->sqlda == (XSQLDA*)0) { - qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory"; + qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory"; return false; } @@ -953,7 +1056,6 @@ bool QIBaseResult::prepare(const QString& query) return true; } - bool QIBaseResult::exec() { Q_D(QIBaseResult); @@ -968,23 +1070,20 @@ bool QIBaseResult::exec() setAt(QSql::BeforeFirstRow); if (d->inda) { - QList<QVariant>& values = boundValues(); - int i; + const QList<QVariant> &values = boundValues(); if (values.count() > d->inda->sqld) { - qWarning() << QLatin1String("QIBaseResult::exec: Parameter mismatch, expected") << - d->inda->sqld << QLatin1String(", got") << values.count() << - QLatin1String("parameters"); + qCWarning(lcIbase) << "QIBaseResult::exec: Parameter mismatch, expected"_L1 + << d->inda->sqld << ", got"_L1 << values.count() + << "parameters"_L1; return false; } - int para = 0; - for (i = 0; i < values.count(); ++i) { - para = i; + for (qsizetype para = 0; para < values.count(); ++para) { if (!d->inda->sqlvar[para].sqldata) // skip unknown datatypes continue; - const QVariant val(values[i]); + const QVariant &val = values[para]; if (d->inda->sqlvar[para].sqltype & 1) { - if (val.isNull()) { + if (QSqlResultPrivate::isVariantNull(val)) { // set null indicator *(d->inda->sqlvar[para].sqlind) = -1; // and set the value to 0, otherwise it would count as empty string. @@ -994,6 +1093,12 @@ bool QIBaseResult::exec() } // a value of 0 means non-null. *(d->inda->sqlvar[para].sqlind) = 0; + } else { + if (QSqlResultPrivate::isVariantNull(val)) { + qCWarning(lcIbase) << "QIBaseResult::exec: Null value replaced by default (zero)"_L1 + << "value for type of column"_L1 << d->inda->sqlvar[para].ownname + << ", which is not nullable."_L1; + } } switch(d->inda->sqlvar[para].sqltype & ~1) { case SQL_INT64: @@ -1030,6 +1135,11 @@ bool QIBaseResult::exec() case SQL_TIMESTAMP: *((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime()); break; +#if (FB_API_VER >= 40) + case SQL_TIMESTAMP_TZ: + *((ISC_TIMESTAMP_TZ*)d->inda->sqlvar[para].sqldata) = toTimeStampTz(val.toDateTime()); + break; +#endif case SQL_TYPE_TIME: *((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime()); break; @@ -1052,8 +1162,8 @@ bool QIBaseResult::exec() *((bool*)d->inda->sqlvar[para].sqldata) = val.toBool(); break; default: - qWarning("QIBaseResult::exec: Unknown datatype %d", - d->inda->sqlvar[para].sqltype & ~1); + qCWarning(lcIbase, "QIBaseResult::exec: Unknown datatype %d", + d->inda->sqlvar[para].sqltype & ~1); break; } } @@ -1165,27 +1275,27 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) // pascal strings - a short with a length information followed by the data row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf); break; - case SQL_INT64: - if (d->sqlda->sqlvar[i].sqlscale < 0) - row[idx] = *(qint64*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale); - else - row[idx] = QVariant(*(qint64*)buf); + case SQL_INT64: { + Q_ASSERT(d->sqlda->sqlvar[i].sqllen == sizeof(qint64)); + const auto val = *(qint64 *)buf; + const auto scale = d->sqlda->sqlvar[i].sqlscale; + row[idx] = applyScale(val, scale); break; + } case SQL_LONG: - if (d->sqlda->sqlvar[i].sqllen == 4) - if (d->sqlda->sqlvar[i].sqlscale < 0) - row[idx] = QVariant(*(qint32*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); - else - row[idx] = QVariant(*(qint32*)buf); - else + if (d->sqlda->sqlvar[i].sqllen == 4) { + const auto val = *(qint32 *)buf; + const auto scale = d->sqlda->sqlvar[i].sqlscale; + row[idx] = applyScale(val, scale); + } else row[idx] = QVariant(*(qint64*)buf); break; - case SQL_SHORT: - if (d->sqlda->sqlvar[i].sqlscale < 0) - row[idx] = QVariant(long((*(short*)buf)) * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); - else - row[idx] = QVariant(int((*(short*)buf))); + case SQL_SHORT: { + const auto val = *(short *)buf; + const auto scale = d->sqlda->sqlvar[i].sqlscale; + row[idx] = applyScale(val, scale); break; + } case SQL_FLOAT: row[idx] = QVariant(double((*(float*)buf))); break; @@ -1213,32 +1323,18 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) case SQL_BOOLEAN: row[idx] = QVariant(bool((*(bool*)buf))); break; +#if (FB_API_VER >= 40) + case SQL_TIMESTAMP_TZ: + row[idx] = fromTimeStampTz(buf); + break; +#endif default: // unknown type - don't even try to fetch + qCWarning(lcIbase, "gotoNext: unknown sqltype: %d", + d->sqlda->sqlvar[i].sqltype & ~1); row[idx] = QVariant(); break; } - if (d->sqlda->sqlvar[i].sqlscale < 0) { - QVariant v = row[idx]; - switch(numericalPrecisionPolicy()) { - case QSql::LowPrecisionInt32: - if (v.convert(QMetaType(QMetaType::Int))) - row[idx]=v; - break; - case QSql::LowPrecisionInt64: - if (v.convert(QMetaType(QMetaType::LongLong))) - row[idx]=v; - break; - case QSql::LowPrecisionDouble: - if (v.convert(QMetaType(QMetaType::Double))) - row[idx]=v; - break; - case QSql::HighPrecision: - if (v.convert(QMetaType(QMetaType::QString))) - row[idx]=v; - break; - } - } } return true; @@ -1319,7 +1415,7 @@ int QIBaseResult::numRowsAffected() bIsProcedure = true; // will sum all changes break; default: - qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")"; + qCWarning(lcIbase) << "numRowsAffected: Unknown statement type (" << d->queryType << ")"; return -1; } @@ -1369,11 +1465,11 @@ QSqlRecord QIBaseResult::record() const if (v.sqlscale < 0) { QSqlQuery q(driver()->createResult()); q.setForwardOnly(true); - q.exec(QLatin1String("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG " + q.exec("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG " "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " - "AND a.RDB$RELATION_NAME = '") + QString::fromLatin1(v.relname, v.relname_length) + QLatin1String("' " - "AND a.RDB$FIELD_NAME = '") + QString::fromLatin1(v.sqlname, v.sqlname_length) + QLatin1String("' ")); + "AND a.RDB$RELATION_NAME = '"_L1 + QString::fromLatin1(v.relname, v.relname_length) + "' " + "AND a.RDB$FIELD_NAME = '"_L1 + QString::fromLatin1(v.sqlname, v.sqlname_length) + "' "_L1); if (q.first()) { if (v.sqlscale < 0) { f.setLength(q.value(0).toInt()); @@ -1385,7 +1481,6 @@ QSqlRecord QIBaseResult::record() const f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional); } } - f.setSqlType(v.sqltype); rec.append(f); } return rec; @@ -1441,27 +1536,27 @@ bool QIBaseDriver::hasFeature(DriverFeature f) const return false; } -bool QIBaseDriver::open(const QString & db, - const QString & user, - const QString & password, - const QString & host, - int port, - const QString & connOpts) +bool QIBaseDriver::open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) { Q_D(QIBaseDriver); if (isOpen()) close(); - const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts)); + const auto opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts)); QByteArray role; - for (int i = 0; i < opts.count(); ++i) { - QString tmp(opts.at(i).simplified()); - int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { - QString val = tmp.mid(idx + 1).simplified(); - QString opt = tmp.left(idx).simplified(); - if (opt.toUpper() == QLatin1String("ISC_DPB_SQL_ROLE_NAME")) { + for (const auto &opt : opts) { + const auto tmp(opt.trimmed()); + qsizetype idx; + if ((idx = tmp.indexOf(u'=')) != -1) { + const auto val = tmp.mid(idx + 1).trimmed(); + const auto opt = tmp.left(idx).trimmed().toString(); + if (opt.toUpper() == "ISC_DPB_SQL_ROLE_NAME"_L1) { role = val.toLocal8Bit(); role.truncate(255); } @@ -1499,7 +1594,7 @@ bool QIBaseDriver::open(const QString & db, QString ldb; if (!host.isEmpty()) - ldb += host + portString + QLatin1Char(':'); + ldb += host + portString + u':'; ldb += db; isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()), &d->ibase, ba.size(), ba.data()); @@ -1511,6 +1606,14 @@ bool QIBaseDriver::open(const QString & db, setOpen(true); setOpenError(false); +#if (FB_API_VER >= 40) + std::call_once(initTZMappingFlag, [d](){ d->initTZMappingCache(); }); + if (lastError().isValid()) + { + setOpen(true); + return false; + } +#endif return true; } @@ -1529,13 +1632,6 @@ void QIBaseDriver::close() qFreeEventBuffer(eBuffer); } d->eventBuffers.clear(); - -#if defined(FB_API_VER) - // TODO check whether this workaround for Firebird crash is still needed - QDeadlineTimer timer(500); - while (!timer.hasExpired()) - QCoreApplication::processEvents(); -#endif } isc_detach_database(d->status, &d->ibase); @@ -1600,28 +1696,28 @@ QStringList QIBaseDriver::tables(QSql::TableType type) const QString typeFilter; if (type == QSql::SystemTables) { - typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0"); + typeFilter += "RDB$SYSTEM_FLAG != 0"_L1; } else if (type == (QSql::SystemTables | QSql::Views)) { - typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL"); + typeFilter += "RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL"_L1; } else { if (!(type & QSql::SystemTables)) - typeFilter += QLatin1String("RDB$SYSTEM_FLAG = 0 AND "); + typeFilter += "RDB$SYSTEM_FLAG = 0 AND "_L1; if (!(type & QSql::Views)) - typeFilter += QLatin1String("RDB$VIEW_BLR IS NULL AND "); + typeFilter += "RDB$VIEW_BLR IS NULL AND "_L1; if (!(type & QSql::Tables)) - typeFilter += QLatin1String("RDB$VIEW_BLR IS NOT NULL AND "); + typeFilter += "RDB$VIEW_BLR IS NOT NULL AND "_L1; if (!typeFilter.isEmpty()) typeFilter.chop(5); } if (!typeFilter.isEmpty()) - typeFilter.prepend(QLatin1String("where ")); + typeFilter.prepend("where "_L1); QSqlQuery q(createResult()); q.setForwardOnly(true); - if (!q.exec(QLatin1String("select rdb$relation_name from rdb$relations ") + typeFilter)) + if (!q.exec("select rdb$relation_name from rdb$relations "_L1 + typeFilter)) return res; - while(q.next()) - res << q.value(0).toString().simplified(); + while (q.next()) + res << q.value(0).toString().simplified(); return res; } @@ -1632,19 +1728,15 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const if (!isOpen()) return rec; + const QString table = stripDelimiters(tablename, QSqlDriver::TableName); QSqlQuery q(createResult()); q.setForwardOnly(true); - QString table = tablename; - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - else - table = table.toUpper(); - q.exec(QLatin1String("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, " + q.exec("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, " "b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG " "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " - "AND a.RDB$RELATION_NAME = '") + table + QLatin1String("' " - "ORDER BY a.RDB$FIELD_POSITION")); + "AND a.RDB$RELATION_NAME = '"_L1 + table + "' " + "ORDER BY a.RDB$FIELD_POSITION"_L1); while (q.next()) { int type = q.value(1).toInt(); @@ -1658,7 +1750,6 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const f.setPrecision(0); } f.setRequired(q.value(5).toInt() > 0); - f.setSqlType(type); rec.append(f); } @@ -1671,23 +1762,18 @@ QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const if (!isOpen()) return index; - QString tablename = table; - if (isIdentifierEscaped(tablename, QSqlDriver::TableName)) - tablename = stripDelimiters(tablename, QSqlDriver::TableName); - else - tablename = tablename.toUpper(); - + const QString tablename = stripDelimiters(table, QSqlDriver::TableName); QSqlQuery q(createResult()); q.setForwardOnly(true); - q.exec(QLatin1String("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE " + q.exec("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE " "FROM RDB$RELATION_CONSTRAINTS a, RDB$INDEX_SEGMENTS b, RDB$RELATION_FIELDS c, RDB$FIELDS d " "WHERE a.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' " - "AND a.RDB$RELATION_NAME = '") + tablename + - QLatin1String(" 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME " + "AND a.RDB$RELATION_NAME = '"_L1 + tablename + + " 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME " "AND c.RDB$RELATION_NAME = a.RDB$RELATION_NAME " "AND c.RDB$FIELD_NAME = b.RDB$FIELD_NAME " "AND d.RDB$FIELD_NAME = c.RDB$FIELD_SOURCE " - "ORDER BY b.RDB$FIELD_POSITION")); + "ORDER BY b.RDB$FIELD_POSITION"_L1); while (q.next()) { QSqlField field(q.value(1).toString().simplified(), @@ -1706,36 +1792,36 @@ QString QIBaseDriver::formatValue(const QSqlField &field, bool trimStrings) cons case QMetaType::QDateTime: { QDateTime datetime = field.value().toDateTime(); if (datetime.isValid()) - return QLatin1Char('\'') + QString::number(datetime.date().year()) + QLatin1Char('-') + - QString::number(datetime.date().month()) + QLatin1Char('-') + - QString::number(datetime.date().day()) + QLatin1Char(' ') + - QString::number(datetime.time().hour()) + QLatin1Char(':') + - QString::number(datetime.time().minute()) + QLatin1Char(':') + - QString::number(datetime.time().second()) + QLatin1Char('.') + - QString::number(datetime.time().msec()).rightJustified(3, QLatin1Char('0'), true) + - QLatin1Char('\''); + return u'\'' + QString::number(datetime.date().year()) + u'-' + + QString::number(datetime.date().month()) + u'-' + + QString::number(datetime.date().day()) + u' ' + + QString::number(datetime.time().hour()) + u':' + + QString::number(datetime.time().minute()) + u':' + + QString::number(datetime.time().second()) + u'.' + + QString::number(datetime.time().msec()).rightJustified(3, u'0', true) + + u'\''; else - return QLatin1String("NULL"); + return "NULL"_L1; } case QMetaType::QTime: { QTime time = field.value().toTime(); if (time.isValid()) - return QLatin1Char('\'') + QString::number(time.hour()) + QLatin1Char(':') + - QString::number(time.minute()) + QLatin1Char(':') + - QString::number(time.second()) + QLatin1Char('.') + - QString::number(time.msec()).rightJustified(3, QLatin1Char('0'), true) + - QLatin1Char('\''); + return u'\'' + QString::number(time.hour()) + u':' + + QString::number(time.minute()) + u':' + + QString::number(time.second()) + u'.' + + QString::number(time.msec()).rightJustified(3, u'0', true) + + u'\''; else - return QLatin1String("NULL"); + return "NULL"_L1; } case QMetaType::QDate: { QDate date = field.value().toDate(); if (date.isValid()) - return QLatin1Char('\'') + QString::number(date.year()) + QLatin1Char('-') + - QString::number(date.month()) + QLatin1Char('-') + - QString::number(date.day()) + QLatin1Char('\''); + return u'\'' + QString::number(date.year()) + u'-' + + QString::number(date.month()) + u'-' + + QString::number(date.day()) + u'\''; else - return QLatin1String("NULL"); + return "NULL"_L1; } default: return QSqlDriver::formatValue(field, trimStrings); @@ -1771,13 +1857,13 @@ bool QIBaseDriver::subscribeToNotification(const QString &name) { Q_D(QIBaseDriver); if (!isOpen()) { - qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open."); + qCWarning(lcIbase, "QIBaseDriver::subscribeFromNotificationImplementation: database not open."); return false; } if (d->eventBuffers.contains(name)) { - qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", - qPrintable(name)); + qCWarning(lcIbase, "QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%ls'.", + qUtf16Printable(name)); return false; } @@ -1818,13 +1904,13 @@ bool QIBaseDriver::unsubscribeFromNotification(const QString &name) { Q_D(QIBaseDriver); if (!isOpen()) { - qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open."); + qCWarning(lcIbase, "QIBaseDriver::unsubscribeFromNotificationImplementation: database not open."); return false; } if (!d->eventBuffers.contains(name)) { - qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.", - qPrintable(name)); + qCWarning(lcIbase, "QIBaseDriver::QIBaseSubscriptionState not subscribed to '%ls'.", + qUtf16Printable(name)); return false; } @@ -1879,8 +1965,8 @@ void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer) (&qEventCallback)), eBuffer->resultBuffer); if (Q_UNLIKELY(status[0] == 1 && status[1])) { - qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'", - qPrintable(i.key())); + qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%ls'", + qUtf16Printable(i.key())); } return; @@ -1891,10 +1977,10 @@ void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer) QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) { + res.replace(u'"', "\"\""_L1); + res.replace(u'.', "\".\""_L1); + res = u'"' + res + u'"'; } return res; } @@ -1906,3 +1992,5 @@ int QIBaseDriver::maximumIdentifierLength(IdentifierType type) const } QT_END_NAMESPACE + +#include "moc_qsql_ibase_p.cpp" diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase_p.h b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h index 9109c2b40f..86c7ae1241 100644 --- a/src/plugins/sqldrivers/ibase/qsql_ibase_p.h +++ b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h @@ -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 #ifndef QSQL_IBASE_H #define QSQL_IBASE_H diff --git a/src/plugins/sqldrivers/mimer/CMakeLists.txt b/src/plugins/sqldrivers/mimer/CMakeLists.txt new file mode 100644 index 0000000000..303af7120c --- /dev/null +++ b/src/plugins/sqldrivers/mimer/CMakeLists.txt @@ -0,0 +1,24 @@ +# Generated from mimer.pro. + +##################################################################### +## MIMERSQLDriverPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QMimerSQLDriverPlugin + OUTPUT_NAME qsqlmimer + PLUGIN_TYPE sqldrivers + SOURCES + main.cpp + qsql_mimer.cpp qsql_mimer.h + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT + LIBRARIES + MimerSQL::MimerSQL + Qt::Core + Qt::SqlPrivate +) + +#### Keys ignored in scope 1:.:.:mimer.pro:<TRUE>: +# OTHER_FILES = "mimer.json" diff --git a/src/plugins/sqldrivers/mimer/README b/src/plugins/sqldrivers/mimer/README new file mode 100644 index 0000000000..02e4c3e162 --- /dev/null +++ b/src/plugins/sqldrivers/mimer/README @@ -0,0 +1,6 @@ +You will need the Mimer SQL development headers and libraries installed before +compiling this plugin. qsql_mimer.h contains an include to mimerapi.h that is +needed for the driver to compile. + +See the Qt SQL documentation for more information on compiling Qt SQL driver +plugins. diff --git a/src/plugins/sqldrivers/mimer/main.cpp b/src/plugins/sqldrivers/mimer/main.cpp new file mode 100644 index 0000000000..560b7da7c7 --- /dev/null +++ b/src/plugins/sqldrivers/mimer/main.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2022 Mimer Information Technology +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qsql_mimer.h" + +#include <qsqldriverplugin.h> +#include <qstringlist.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QMimerSQLDriverPlugin : public QSqlDriverPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSqlDriverFactoryInterface" FILE "mimer.json") +public: + QMimerSQLDriverPlugin(); + QSqlDriver *create(const QString &) override; +}; + +QMimerSQLDriverPlugin::QMimerSQLDriverPlugin() : QSqlDriverPlugin() { } + +QSqlDriver *QMimerSQLDriverPlugin::create(const QString &name) +{ + if (name == "QMIMER"_L1) + return new QMimerSQLDriver; + return nullptr; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/sqldrivers/mimer/mimer.json b/src/plugins/sqldrivers/mimer/mimer.json new file mode 100644 index 0000000000..fba96b765d --- /dev/null +++ b/src/plugins/sqldrivers/mimer/mimer.json @@ -0,0 +1,5 @@ +{ + "Keys": [ + "QMIMER" + ] +} diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.cpp b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp new file mode 100644 index 0000000000..7f89e0a0d5 --- /dev/null +++ b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp @@ -0,0 +1,1610 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2022 Mimer Information Technology +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qmetatype.h> +#include <qdatetime.h> +#include <qloggingcategory.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qsqlquery.h> +#include <qsocketnotifier.h> +#include <qstringlist.h> +#include <qlocale.h> +#if defined(Q_OS_WIN32) +# include <QtCore/qt_windows.h> +#endif +#include <QtSql/private/qsqlresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include "qsql_mimer.h" + +#define MIMER_DEFAULT_DATATYPE 1000 + +Q_DECLARE_OPAQUE_POINTER(MimerSession) +Q_DECLARE_METATYPE(MimerSession) + +Q_DECLARE_OPAQUE_POINTER(MimerStatement) +Q_DECLARE_METATYPE(MimerStatement) + +QT_BEGIN_NAMESPACE + +static Q_LOGGING_CATEGORY(lcMimer, "qt.sql.mimer") + +enum class MimerColumnTypes { + Binary, + Clob, + Blob, + String, + Int, + Numeric, + Long, + Float, + Double, + Boolean, + Uuid, + Date, + Time, + Timestamp, + Unknown +}; + +using namespace Qt::StringLiterals; + +class QMimerSQLResultPrivate; + +class QMimerSQLResult final : public QSqlResult +{ + Q_DECLARE_PRIVATE(QMimerSQLResult) +public: + QMimerSQLResult(const QMimerSQLDriver *db); + virtual ~QMimerSQLResult() override; + QVariant handle() const override; + static constexpr int genericError = -1; + static constexpr int lobChunkMaxSizeSet = 1048500; + static constexpr int lobChunkMaxSizeFetch = 65536; + static constexpr int maxStackStringSize = 200; + static constexpr int maxTimeStringSize = 18; + static constexpr int maxDateStringSize = 10; + static constexpr int maxTimestampStringSize = 29; + +private: + void cleanup(); + bool fetch(int i) override; + bool fetchFirst() override; + bool fetchLast() override; + bool fetchNext() override; + QVariant data(int i) override; + bool isNull(int index) override; + bool reset(const QString &query) override; + int size() override; + int numRowsAffected() override; + QSqlRecord record() const override; + bool prepare(const QString &query) override; + bool execBatch(bool arrayBind = false) override; + bool exec() override; + qint64 currentRow(); + QVariant lastInsertId() const override; +}; + +class QMimerSQLDriverPrivate final : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QMimerSQLDriver) +public: + QMimerSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MimerSQL), sessionhandle(nullptr) { } + MimerSession sessionhandle; + QString dbName; + QString dbUser; + void splitTableQualifier(const QString &qualifier, QString *schema, QString *table) const; +}; + +class QMimerSQLResultPrivate : public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QMimerSQLResult) +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QMimerSQLDriver) + QMimerSQLResultPrivate(QMimerSQLResult *q, const QMimerSQLDriver *drv) + : QSqlResultPrivate(q, drv), + statementhandle(nullptr), + lobhandle(nullptr), + rowsAffected(0), + preparedQuery(false), + openCursor(false), + openStatement(false), + executedStatement(false), + callWithOut(false), + execBatch(false), + currentRow(QSql::BeforeFirstRow) + { + } + MimerStatement statementhandle; + MimerLob lobhandle; + int rowsAffected; + bool preparedQuery; + bool openCursor; + bool openStatement; + bool executedStatement; + bool callWithOut; + bool execBatch; + qint64 currentSize = -1; + qint64 currentRow; // Only used when forwardOnly() + QVector<QVariant> batch_vector; +}; + +static QSqlError qMakeError(const QString &err, const int errCode, QSqlError::ErrorType type, + const QMimerSQLDriverPrivate *p) +{ + QString msg; + if (p) { + size_t str_len; + int e_code; + int rc; + str_len = (rc = MimerGetError(p->sessionhandle, &e_code, NULL, 0)) + 1; + if (!MIMER_SUCCEEDED(rc)) { + msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1") + .arg(errCode); + } else { + QVarLengthArray<wchar_t> tmp_buff((qsizetype)str_len); + if (!MIMER_SUCCEEDED( + rc = MimerGetError(p->sessionhandle, &e_code, tmp_buff.data(), str_len))) + msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1") + .arg(errCode); + else + msg = QString::fromWCharArray(tmp_buff.data()); + } + } else { + msg = QCoreApplication::translate("QMimerSQL", "Generic Mimer SQL error"); + } + + return QSqlError("QMIMER: "_L1 + err, msg, type, QString::number(errCode)); +} + +static QString msgCouldNotGet(const char *type, int column) +{ + //: Data type, column + return QCoreApplication::translate("QMimerSQLResult", + "Could not get %1, column %2").arg(QLatin1StringView(type)).arg(column); +} + +static QString msgCouldNotSet(const char *type, int column) +{ + //: Data type, parameter + return QCoreApplication::translate("QMimerSQLResult", + "Could not set %1, parameter %2").arg(QLatin1StringView(type)).arg(column); +} + +QMimerSQLDriver::QMimerSQLDriver(QObject *parent) : QSqlDriver(*new QMimerSQLDriverPrivate, parent) +{ +} + +QMimerSQLDriver::QMimerSQLDriver(MimerSession *conn, QObject *parent) + : QSqlDriver(*new QMimerSQLDriverPrivate, parent) +{ + Q_D(QMimerSQLDriver); + if (conn) + d->sessionhandle = *conn; +} + +QMimerSQLDriver::~QMimerSQLDriver() +{ + close(); +} + +QMimerSQLResult::QMimerSQLResult(const QMimerSQLDriver *db) + : QSqlResult(*new QMimerSQLResultPrivate(this, db)) +{ + Q_D(QMimerSQLResult); + d->preparedQuery = db->hasFeature(QSqlDriver::PreparedQueries); +} + +QMimerSQLResult::~QMimerSQLResult() +{ + cleanup(); +} + +static MimerColumnTypes mimerMapColumnTypes(int32_t t) +{ + switch (t) { + case MIMER_BINARY: + case MIMER_BINARY_VARYING: + return MimerColumnTypes::Binary; + case MIMER_BLOB: + case MIMER_NATIVE_BLOB: + return MimerColumnTypes::Blob; + case MIMER_CLOB: + case MIMER_NCLOB: + case MIMER_NATIVE_CLOB: + case MIMER_NATIVE_NCLOB: + return MimerColumnTypes::Clob; + case MIMER_DATE: + return MimerColumnTypes::Date; + case MIMER_TIME: + return MimerColumnTypes::Time; + case MIMER_TIMESTAMP: + return MimerColumnTypes::Timestamp; + case MIMER_INTERVAL_DAY: + case MIMER_INTERVAL_DAY_TO_HOUR: + case MIMER_INTERVAL_DAY_TO_MINUTE: + case MIMER_INTERVAL_DAY_TO_SECOND: + case MIMER_INTERVAL_HOUR: + case MIMER_INTERVAL_HOUR_TO_MINUTE: + case MIMER_INTERVAL_HOUR_TO_SECOND: + case MIMER_INTERVAL_MINUTE: + case MIMER_INTERVAL_MINUTE_TO_SECOND: + case MIMER_INTERVAL_MONTH: + case MIMER_INTERVAL_SECOND: + case MIMER_INTERVAL_YEAR: + case MIMER_INTERVAL_YEAR_TO_MONTH: + case MIMER_NCHAR: + case MIMER_CHARACTER: + case MIMER_CHARACTER_VARYING: + case MIMER_NCHAR_VARYING: + case MIMER_UTF8: + case MIMER_DEFAULT_DATATYPE: + return MimerColumnTypes::String; + case MIMER_INTEGER: + case MIMER_DECIMAL: + case MIMER_FLOAT: + return MimerColumnTypes::Numeric; + case MIMER_BOOLEAN: + return MimerColumnTypes::Boolean; + case MIMER_T_BIGINT: + case MIMER_T_UNSIGNED_BIGINT: + case MIMER_NATIVE_BIGINT_NULLABLE: + case MIMER_NATIVE_BIGINT: + return MimerColumnTypes::Long; + case MIMER_NATIVE_REAL_NULLABLE: + case MIMER_NATIVE_REAL: + case MIMER_T_REAL: + return MimerColumnTypes::Float; + case MIMER_T_FLOAT: + case MIMER_NATIVE_DOUBLE_NULLABLE: + case MIMER_NATIVE_DOUBLE: + case MIMER_T_DOUBLE: + return MimerColumnTypes::Double; + case MIMER_NATIVE_INTEGER: + case MIMER_NATIVE_INTEGER_NULLABLE: + case MIMER_NATIVE_SMALLINT_NULLABLE: + case MIMER_NATIVE_SMALLINT: + case MIMER_T_INTEGER: + case MIMER_T_SMALLINT: + return MimerColumnTypes::Int; + case MIMER_UUID: + return MimerColumnTypes::Uuid; + default: + qCWarning(lcMimer) << "QMimerSQLDriver::mimerMapColumnTypes: Unknown data type:" << t; + } + return MimerColumnTypes::Unknown; +} + +static QMetaType::Type qDecodeMSQLType(int32_t t) +{ + switch (t) { + case MIMER_BINARY: + case MIMER_BINARY_VARYING: + case MIMER_BLOB: + case MIMER_NATIVE_BLOB: + return QMetaType::QByteArray; + case MIMER_CLOB: + case MIMER_NCLOB: + case MIMER_NATIVE_CLOB: + case MIMER_NATIVE_NCLOB: + case MIMER_INTERVAL_DAY: + case MIMER_DECIMAL: + case MIMER_INTERVAL_DAY_TO_HOUR: + case MIMER_INTERVAL_DAY_TO_MINUTE: + case MIMER_INTERVAL_DAY_TO_SECOND: + case MIMER_INTERVAL_HOUR: + case MIMER_INTERVAL_HOUR_TO_MINUTE: + case MIMER_INTERVAL_HOUR_TO_SECOND: + case MIMER_INTERVAL_MINUTE: + case MIMER_INTERVAL_MINUTE_TO_SECOND: + case MIMER_INTERVAL_MONTH: + case MIMER_INTERVAL_SECOND: + case MIMER_INTERVAL_YEAR: + case MIMER_INTERVAL_YEAR_TO_MONTH: + case MIMER_NCHAR: + case MIMER_CHARACTER: + case MIMER_CHARACTER_VARYING: + case MIMER_NCHAR_VARYING: + case MIMER_UTF8: + case MIMER_DEFAULT_DATATYPE: + case MIMER_INTEGER: + case MIMER_FLOAT: + return QMetaType::QString; + case MIMER_BOOLEAN: + return QMetaType::Bool; + case MIMER_T_BIGINT: + case MIMER_T_UNSIGNED_BIGINT: + case MIMER_NATIVE_BIGINT_NULLABLE: + case MIMER_NATIVE_BIGINT: + return QMetaType::LongLong; + case MIMER_NATIVE_REAL_NULLABLE: + case MIMER_NATIVE_REAL: + case MIMER_T_REAL: + return QMetaType::Float; + case MIMER_T_FLOAT: + case MIMER_NATIVE_DOUBLE_NULLABLE: + case MIMER_NATIVE_DOUBLE: + case MIMER_T_DOUBLE: + return QMetaType::Double; + case MIMER_NATIVE_INTEGER_NULLABLE: + case MIMER_T_INTEGER: + case MIMER_NATIVE_INTEGER: + return QMetaType::Int; + case MIMER_NATIVE_SMALLINT_NULLABLE: + case MIMER_T_SMALLINT: + return QMetaType::Int; + case MIMER_DATE: + return QMetaType::QDate; + case MIMER_TIME: + return QMetaType::QTime; + break; + case MIMER_TIMESTAMP: + return QMetaType::QDateTime; + case MIMER_UUID: + return QMetaType::QUuid; + default: + qCWarning(lcMimer) << "QMimerSQLDriver::qDecodeMSQLType: Unknown data type:" << t; + return QMetaType::UnknownType; + } +} + +static int32_t qLookupMimDataType(QStringView s) +{ + if (s == u"BINARY") + return MIMER_BINARY; + if (s == u"BINARY VARYING") + return MIMER_BINARY_VARYING; + if (s == u"BINARY LARGE OBJECT") + return MIMER_BLOB; + if (s == u"CHARACTER LARGE OBJECT") + return MIMER_CLOB; + if (s == u"NATIONAL CHAR LARGE OBJECT") + return MIMER_NCLOB; + if (s == u"INTERVAL DAY") + return MIMER_INTERVAL_DAY; + if (s == u"DECIMAL") + return MIMER_DECIMAL; + if (s == u"INTERVAL DAY TO HOUR") + return MIMER_INTERVAL_DAY_TO_HOUR; + if (s == u"INTERVAL DAY TO MINUTE") + return MIMER_INTERVAL_DAY_TO_MINUTE; + if (s == u"INTERVAL DAY TO SECOND") + return MIMER_INTERVAL_DAY_TO_SECOND; + if (s == u"INTERVAL HOUR") + return MIMER_INTERVAL_HOUR; + if (s == u"INTERVAL HOUR TO MINUTE") + return MIMER_INTERVAL_HOUR_TO_MINUTE; + if (s == u"INTERVAL HOUR TO SECOND") + return MIMER_INTERVAL_HOUR_TO_SECOND; + if (s == u"INTERVAL MINUTE") + return MIMER_INTERVAL_MINUTE; + if (s == u"INTERVAL MINUTE TO SECOND") + return MIMER_INTERVAL_MINUTE_TO_SECOND; + if (s == u"INTERVAL MONTH") + return MIMER_INTERVAL_MONTH; + if (s == u"INTERVAL SECOND") + return MIMER_INTERVAL_SECOND; + if (s == u"INTERVAL YEAR") + return MIMER_INTERVAL_YEAR; + if (s == u"INTERVAL YEAR TO MONTH") + return MIMER_INTERVAL_YEAR_TO_MONTH; + if (s == u"NATIONAL CHARACTER") + return MIMER_NCHAR; + if (s == u"CHARACTER") + return MIMER_CHARACTER; + if (s == u"CHARACTER VARYING") + return MIMER_CHARACTER_VARYING; + if (s == u"NATIONAL CHARACTER VARYING") + return MIMER_NCHAR_VARYING; + if (s == u"UTF-8") + return MIMER_UTF8; + if (s == u"BOOLEAN") + return MIMER_BOOLEAN; + if (s == u"BIGINT") + return MIMER_T_BIGINT; + if (s == u"REAL") + return MIMER_T_REAL; + if (s == u"FLOAT") + return MIMER_T_FLOAT; + if (s == u"DOUBLE PRECISION") + return MIMER_T_DOUBLE; + if (s == u"INTEGER") + return MIMER_T_INTEGER; + if (s == u"SMALLINT") + return MIMER_T_SMALLINT; + if (s == u"DATE") + return MIMER_DATE; + if (s == u"TIME") + return MIMER_TIME; + if (s == u"TIMESTAMP") + return MIMER_TIMESTAMP; + if (s == u"BUILTIN.UUID") + return MIMER_UUID; + if (s == u"USER-DEFINED") + return MIMER_DEFAULT_DATATYPE; + qCWarning(lcMimer) << "QMimerSQLDriver::qLookupMimDataType: Unhandled data type:" << s; + return MIMER_DEFAULT_DATATYPE; +} + +QVariant QMimerSQLResult::handle() const +{ + Q_D(const QMimerSQLResult); + return QVariant::fromValue(d->statementhandle); +} + +void QMimerSQLResult::cleanup() +{ + Q_D(QMimerSQLResult); + if (!driver() || !driver()->isOpen()) { + d->openCursor = false; + d->openStatement = false; + return; + } + if (d->openCursor) { + const int32_t err = MimerCloseCursor(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not close cursor"), err, + QSqlError::StatementError, d->drv_d_func())); + d->openCursor = false; + } + if (d->openStatement) { + const int32_t err = MimerEndStatement(&d->statementhandle); + if (!MIMER_SUCCEEDED(err)) + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not close statement"), + err, QSqlError::StatementError, d->drv_d_func())); + d->openStatement = false; + } + d->currentSize = -1; +} + +qint64 QMimerSQLResult::currentRow() +{ + Q_D(const QMimerSQLResult); + return d->currentRow; +} + +bool QMimerSQLResult::fetch(int i) +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (i == at()) + return true; + if (i < 0) + return false; + + if (isForwardOnly() && i < at()) + return false; + + if (isForwardOnly()) { + bool rc; + do { + rc = fetchNext(); + } while (rc && currentRow() < i); + return rc; + } else { + err = MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, i + 1); + if (err == MIMER_NO_DATA) + return false; + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(QCoreApplication::translate("QMimerSQLResult", "Fetch did not succeed"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + setAt(MimerCurrentRow(d->statementhandle) - 1); + return true; +} + +bool QMimerSQLResult::fetchFirst() +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) { + if (currentRow() < 0) + return fetchNext(); + else if (currentRow() == 0) + setAt(0); + else + return false; + } else { + err = MimerFetchScroll(d->statementhandle, MIMER_FIRST, 0); + if (MIMER_SUCCEEDED(err) && err != MIMER_NO_DATA) + setAt(0); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Fetch first did not succeed"), err, + QSqlError::StatementError, d->drv_d_func())); + return false; + } + if (err == MIMER_NO_DATA) + return false; + return true; +} + +bool QMimerSQLResult::fetchLast() +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + int row = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) { + bool rc; + do { + rc = fetchNext(); + } while (rc); + + return currentRow() >= 0; + } else { + err = MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0); + if (err == MIMER_NO_DATA) + return false; + if (MIMER_SUCCEEDED(err)) { + row = MimerCurrentRow(d->statementhandle) - 1; + } else { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Fetch last did not succeed"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } + + if (row < 0) { + setAt(QSql::BeforeFirstRow); + return false; + } else { + setAt(row); + return true; + } +} + +bool QMimerSQLResult::fetchNext() +{ + Q_D(QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) + err = MimerFetch(d->statementhandle); + else + err = MimerFetchScroll(d->statementhandle, MIMER_NEXT, 0); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not fetch next row"), err, + QSqlError::StatementError, d->drv_d_func())); + if (isForwardOnly()) + d->currentRow = QSql::BeforeFirstRow; + return false; + } + if (err == MIMER_NO_DATA) + return false; + if (isForwardOnly()) + setAt(++d->currentRow); + else + setAt(MimerCurrentRow(d->statementhandle) - 1); + return true; +} + +QVariant QMimerSQLResult::data(int i) +{ + Q_D(QMimerSQLResult); + int32_t err; + int32_t mType; + if (d->callWithOut) { + if (i >= MimerParameterCount(d->statementhandle)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return QVariant(); + } + mType = MimerParameterType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + } else { + if (i >= MimerColumnCount(d->statementhandle)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return QVariant(); + } + mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + } + const QMetaType::Type type = qDecodeMSQLType(mType); + const MimerColumnTypes mimDataType = mimerMapColumnTypes(mType); + err = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(i + 1)); + if (err > 0) { + return QVariant(QMetaType(type), nullptr); + } else { + switch (mimDataType) { + case MimerColumnTypes::Date: { + wchar_t dateString_w[maxDateStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), dateString_w, + sizeof(dateString_w) / sizeof(dateString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("date", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return QDate::fromString(QString::fromWCharArray(dateString_w), "yyyy-MM-dd"_L1); + } + case MimerColumnTypes::Time: { + wchar_t timeString_w[maxTimeStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), timeString_w, + sizeof(timeString_w) / sizeof(timeString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + QString timeString = QString::fromWCharArray(timeString_w); + QString timeFormatString = "HH:mm:ss"_L1; + if (timeString.size() > 8) { + timeFormatString.append(".zzz"_L1); + timeString = timeString.left(12); + } + return QTime::fromString(timeString, timeFormatString); + } + case MimerColumnTypes::Timestamp: { + wchar_t dateTimeString_w[maxTimestampStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + dateTimeString_w, + sizeof(dateTimeString_w) / sizeof(dateTimeString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("date time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + QString dateTimeString = QString::fromWCharArray(dateTimeString_w); + QString dateTimeFormatString = "yyyy-MM-dd HH:mm:ss"_L1; + if (dateTimeString.size() > 19) { + dateTimeFormatString.append(".zzz"_L1); + dateTimeString = dateTimeString.left(23); + } + return QDateTime::fromString(dateTimeString, dateTimeFormatString); + } + case MimerColumnTypes::Int: { + int resInt; + err = MimerGetInt32(d->statementhandle, static_cast<std::int16_t>(i + 1), &resInt); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("int32", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return resInt; + } + case MimerColumnTypes::Long: { + int64_t resLongLong; + err = MimerGetInt64(d->statementhandle, static_cast<std::int16_t>(i + 1), &resLongLong); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("int64", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return (qlonglong)resLongLong; + } + case MimerColumnTypes::Boolean: { + err = MimerGetBoolean(d->statementhandle, static_cast<std::int16_t>(i + 1)); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("boolean", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return err == 1; + } + case MimerColumnTypes::Float: { + float resFloat; + err = MimerGetFloat(d->statementhandle, static_cast<std::int16_t>(i + 1), &resFloat); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("float", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return resFloat; + } + case MimerColumnTypes::Double: { + double resDouble; + err = MimerGetDouble(d->statementhandle, static_cast<std::int16_t>(i + 1), &resDouble); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("double", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + switch (numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + return static_cast<std::int32_t>(resDouble); + case QSql::LowPrecisionInt64: + return static_cast<qint64>(resDouble); + case QSql::LowPrecisionDouble: + return static_cast<qreal>(resDouble); + case QSql::HighPrecision: + return QString::number(resDouble, 'g', 17); + } + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Binary: { + QByteArray byteArray; + // Get size + err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), NULL, 0); + if (MIMER_SUCCEEDED(err)) { + byteArray.resize(err); + err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), + byteArray.data(), err); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("binary", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return byteArray; + } + case MimerColumnTypes::Blob: { + QByteArray byteArray; + size_t size; + err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size, + &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeFetch; + QVarLengthArray<char> blobchar(lobChunkMaxSizeFetch); + byteArray.reserve(size); + size_t left_to_return = size; + while (left_to_return > 0) { + const size_t bytesToReceive = + left_to_return <= maxSize ? left_to_return : maxSize; + err = MimerGetBlobData(&d->lobhandle, blobchar.data(), bytesToReceive); + byteArray.append(QByteArray::fromRawData(blobchar.data(), bytesToReceive)); + left_to_return -= bytesToReceive; + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("BLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + } + } else { + setLastError(qMakeError(msgCouldNotGet("BLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return byteArray; + } + case MimerColumnTypes::Numeric: + case MimerColumnTypes::String: { + wchar_t resString_w[maxStackStringSize + 1]; + // Get size + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), resString_w, + 0); + if (MIMER_SUCCEEDED(err)) { + int size = err; + if (err <= maxStackStringSize) { // For smaller strings, use a small buffer for + // efficiency + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + resString_w, maxStackStringSize + 1); + if (MIMER_SUCCEEDED(err)) + return QString::fromWCharArray(resString_w); + } else { // For larger strings, dynamically allocate memory + QVarLengthArray<wchar_t> largeResString_w(size + 1); + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + largeResString_w.data(), size + 1); + if (MIMER_SUCCEEDED(err)) + return QString::fromWCharArray(largeResString_w.data()); + } + } + setLastError(qMakeError(msgCouldNotGet( + mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Clob: { + size_t size; + err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size, + &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeFetch; + QVarLengthArray<wchar_t> clobstring_w(lobChunkMaxSizeFetch + 1); + + size_t left_to_return = size; + QString returnString; + while (left_to_return > 0) { + const size_t bytesToReceive = + left_to_return <= maxSize ? left_to_return : maxSize; + err = MimerGetNclobData(&d->lobhandle, clobstring_w.data(), bytesToReceive + 1); + returnString.append(QString::fromWCharArray(clobstring_w.data())); + left_to_return -= bytesToReceive; + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + } + return returnString; + } + setLastError(qMakeError(msgCouldNotGet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Uuid: { + unsigned char uuidChar[16]; + err = MimerGetUUID(d->statementhandle, static_cast<std::int16_t>(i + 1), uuidChar); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("UUID", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + const QByteArray uuidByteArray = QByteArray(reinterpret_cast<char *>(uuidChar), 16); + return QUuid::fromRfc4122(uuidByteArray); + } + case MimerColumnTypes::Unknown: + default: + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Unknown data type %1").arg(i), + genericError, QSqlError::StatementError, nullptr)); + } + return QVariant(QMetaType(type), nullptr); + } +} + +bool QMimerSQLResult::isNull(int index) +{ + Q_D(const QMimerSQLResult); + const int32_t rc = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(index + 1)); + if (!MIMER_SUCCEEDED(rc)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not check null, column %1") + .arg(index), + rc, QSqlError::StatementError, d->drv_d_func())); + return false; + } + return rc != 0; +} + +bool QMimerSQLResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +int QMimerSQLResult::size() +{ + Q_D(QMimerSQLResult); + if (!isActive() || !isSelect() || isForwardOnly()) + return -1; + + if (d->currentSize != -1) + return d->currentSize; + + const int currentRow = MimerCurrentRow(d->statementhandle); + MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0); + int size = MimerCurrentRow(d->statementhandle); + if (!MIMER_SUCCEEDED(size)) + size = -1; + MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, currentRow); + d->currentSize = size; + return size; +} + +int QMimerSQLResult::numRowsAffected() +{ + Q_D(const QMimerSQLResult); + return d->rowsAffected; +} + +QSqlRecord QMimerSQLResult::record() const +{ + Q_D(const QMimerSQLResult); + QSqlRecord rec; + if (!isActive() || !isSelect() || !driver()) + return rec; + QSqlField field; + const int colSize = MimerColumnCount(d->statementhandle); + for (int i = 0; i < colSize; i++) { + wchar_t colName_w[100]; + MimerColumnName(d->statementhandle, static_cast<std::int16_t>(i + 1), colName_w, + sizeof(colName_w) / sizeof(colName_w[0])); + field.setName(QString::fromWCharArray(colName_w)); + const int32_t mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + const QMetaType::Type type = qDecodeMSQLType(mType); + field.setMetaType(QMetaType(type)); + field.setValue(QVariant(field.metaType())); + // field.setPrecision(); Should be implemented once the Mimer API can give this + // information. + // field.setLength(); Should be implemented once the Mimer API can give + // this information. + rec.insert(i, field); + } + return rec; +} + +bool QMimerSQLResult::prepare(const QString &query) +{ + Q_D(QMimerSQLResult); + int32_t err; + if (!driver()) + return false; + if (!d->preparedQuery) + return QSqlResult::prepare(query); + if (query.isEmpty()) + return false; + cleanup(); + const int option = isForwardOnly() ? MIMER_FORWARD_ONLY : MIMER_SCROLLABLE; + err = MimerBeginStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData(), option, + &d->statementhandle); + if (err == MIMER_STATEMENT_CANNOT_BE_PREPARED) { + err = MimerExecuteStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData()); + if (MIMER_SUCCEEDED(err)) { + d->executedStatement = true; + d->openCursor = false; + d->openStatement = false; + return true; + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(QCoreApplication::translate("QMimerSQLResult", + "Could not prepare/execute statement"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + d->openStatement = true; + return true; +} + +bool QMimerSQLResult::exec() +{ + Q_D(QMimerSQLResult); + int32_t err; + if (!driver()) + return false; + if (!d->preparedQuery) + return QSqlResult::exec(); + if (d->executedStatement) { + d->executedStatement = false; + return true; + } + if (d->openCursor) { + setAt(QSql::BeforeFirstRow); + err = MimerCloseCursor(d->statementhandle); + d->openCursor = false; + d->currentSize = -1; + } + QVector<QVariant> &values = boundValues(); + if (d->execBatch) + values = d->batch_vector; + int mimParamCount = MimerParameterCount(d->statementhandle); + if (!MIMER_SUCCEEDED(mimParamCount)) + mimParamCount = 0; + if (mimParamCount != values.size()) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Wrong number of parameters"), + genericError, QSqlError::StatementError, nullptr)); + return false; + } + for (int i = 0; i < mimParamCount; i++) { + if (bindValueType(i) == QSql::Out) { + d->callWithOut = true; + continue; + } + const QVariant &val = values.at(i); + if (QSqlResultPrivate::isVariantNull(val) || val.isNull() || val.toString().isNull()) { + err = MimerSetNull(d->statementhandle, i + 1); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("null", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + continue; + } + + const int mimParamType = MimerParameterType(d->statementhandle, i + 1); + const MimerColumnTypes mimDataType = mimerMapColumnTypes(mimParamType); + switch (mimDataType) { + case MimerColumnTypes::Int: { + bool convertOk; + err = MimerSetInt32(d->statementhandle, i + 1, val.toInt(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("int32", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Long: { + bool convertOk; + err = MimerSetInt64(d->statementhandle, i + 1, val.toLongLong(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("int64", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Float: { + bool convertOk; + err = MimerSetFloat(d->statementhandle, i + 1, val.toFloat(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("float", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Double: { + bool convertOk; + err = MimerSetDouble(d->statementhandle, i + 1, val.toDouble(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("double", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Binary: { + const QByteArray binArr = val.toByteArray(); + size_t size = static_cast<std::size_t>(binArr.size()); + err = MimerSetBinary(d->statementhandle, i + 1, binArr.data(), size); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("binary", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Boolean: { + err = MimerSetBoolean(d->statementhandle, i + 1, val.toBool() == true ? 1 : 0); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("boolean", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Uuid: { + const QByteArray uuidArray = + QByteArray::fromHex(val.toUuid().toString(QUuid::WithoutBraces).toLatin1()); + const unsigned char *uuid = + reinterpret_cast<const unsigned char *>(uuidArray.constData()); + err = MimerSetUUID(d->statementhandle, i + 1, uuid); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("UUID", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Numeric: + case MimerColumnTypes::String: { + QByteArray string_b = val.toString().trimmed().toUtf8(); + const char *string_u = string_b.constData(); + err = MimerSetString8(d->statementhandle, i + 1, string_u); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet( + mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Date: { + err = MimerSetString8(d->statementhandle, i + 1, val.toString().toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("date", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Time: { + QString timeFormatString = "hh:mm:ss"_L1; + const QTime timeVal = val.toTime(); + if (timeVal.msec() > 0) + timeFormatString.append(".zzz"_L1); + err = MimerSetString8(d->statementhandle, i + 1, + timeVal.toString(timeFormatString).toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Timestamp: { + QString dateTimeFormatString = "yyyy-MM-dd hh:mm:ss"_L1; + const QDateTime dateTimeVal = val.toDateTime(); + if (dateTimeVal.time().msec() > 0) + dateTimeFormatString.append(".zzz"_L1); + err = MimerSetString8( + d->statementhandle, i + 1, + val.toDateTime().toString(dateTimeFormatString).toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("datetime", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Blob: { + QByteArray blobArr = val.toByteArray(); + const char *blobData = blobArr.constData(); + qsizetype size = blobArr.size(); + err = MimerSetLob(d->statementhandle, i + 1, size, &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + qsizetype maxSize = lobChunkMaxSizeSet; + if (size > maxSize) { + qsizetype left_to_send = size; + for (qsizetype k = 0; left_to_send > 0; k++) { + if (left_to_send <= maxSize) { + err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], + left_to_send); + left_to_send = 0; + } else { + err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], maxSize); + left_to_send = left_to_send - maxSize; + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("BLOB byte array", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } else { + err = MimerSetBlobData(&d->lobhandle, blobArr, size); + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("BLOB byte array", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Clob: { + QByteArray string_b = val.toString().trimmed().toUtf8(); + const char *string_u = string_b.constData(); + size_t size_c = 1; + size_t size = 0; + while (string_u[size++]) + if ((string_u[size] & 0xc0) != 0x80) + size_c++; + err = MimerSetLob(d->statementhandle, i + 1, size_c, &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeSet; + if (size > maxSize) { + size_t left_to_send = size; + size_t pos = 0; + uint step_back = 0; + while (left_to_send > 0 && step_back < maxSize) { + step_back = 0; + if (left_to_send <= maxSize) { + err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], left_to_send); + left_to_send = 0; + } else { + // Check that we don't split a multi-byte utf-8 characters + while (pos + maxSize - step_back > 0 + && (string_u[pos + maxSize - step_back] & 0xc0) == 0x80) + step_back++; + err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], + maxSize - step_back); + left_to_send = left_to_send - maxSize + step_back; + pos += maxSize - step_back; + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } + } else { + err = MimerSetNclobData8(&d->lobhandle, string_u, size); + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Unknown: + default: + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Unknown datatype, parameter %1") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return false; + } + } + if (d->execBatch) + return true; + err = MimerExecute(d->statementhandle); + if (MIMER_SUCCEEDED(err)) { + d->rowsAffected = err; + int k = 0; + for (qsizetype i = 0; i < values.size(); i++) { + if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) { + bindValue(i, data(k), QSql::In); + k++; + } + } + d->callWithOut = false; + } + setSelect(false); + if (MIMER_SEQUENCE_ERROR == err) { + err = MimerOpenCursor(d->statementhandle); + d->rowsAffected = err; + d->openCursor = true; + d->currentRow = QSql::BeforeFirstRow; + setSelect(true); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(QCoreApplication::translate("QMimerSQLResult", + "Could not execute statement/open cursor"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + setActive(true); + return true; +} + +bool QMimerSQLResult::execBatch(bool arrayBind) +{ + Q_D(QMimerSQLResult); + Q_UNUSED(arrayBind); + int32_t err; + const QVector<QVariant> values = boundValues(); + + // Check that we only have input parameters. Currently + // we can only handle batch operations without output parameters. + for (qsizetype i = 0; i < values.first().toList().size(); i++) + if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) { + setLastError(qMakeError(QCoreApplication::translate( + "QMimerSQLResult", + "Only input parameters can be used in batch operations"), + genericError, QSqlError::StatementError, nullptr)); + d->execBatch = false; + return false; + } + d->execBatch = true; + for (qsizetype i = 0; i < values.first().toList().size(); i++) { + for (qsizetype j = 0; j < values.size(); j++) + d->batch_vector.append(values.at(j).toList().at(i)); + exec(); + if (i != (values.at(0).toList().size() - 1)) { + err = MimerAddBatch(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + //: %1 is the batch number + QCoreApplication::translate("QMimerSQLResult", "Could not add batch %1") + .arg(i), + err, QSqlError::StatementError, d->drv_d_func())); + d->execBatch = false; + return false; + } + } + d->batch_vector.clear(); + } + d->execBatch = false; + err = MimerExecute(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not execute batch"), err, + QSqlError::StatementError, d->drv_d_func())); + return false; + } + return true; +} + +QVariant QMimerSQLResult::lastInsertId() const +{ + Q_D(const QMimerSQLResult); + int64_t lastSequence; + const int32_t err = MimerGetSequenceInt64(d->statementhandle, &lastSequence); + if (!MIMER_SUCCEEDED(err)) + return QVariant(QMetaType(QMetaType::LongLong), nullptr); + return QVariant(qint64(lastSequence)); +} + +bool QMimerSQLDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case NamedPlaceholders: // Is true in reality but Qt parses Sql statement... + case EventNotifications: + case LowPrecisionNumbers: + case MultipleResultSets: + case SimpleLocking: + case CancelQuery: + return false; + case FinishQuery: + case LastInsertId: + case Transactions: + case QuerySize: + case BLOB: + case Unicode: + case PreparedQueries: + case PositionalPlaceholders: + case BatchOperations: + return true; + } + return true; +} + +bool QMimerSQLDriver::open(const QString &db, const QString &user, const QString &password, + const QString &host, int port, const QString &connOpts) +{ + Q_D(QMimerSQLDriver); + Q_UNUSED(host); + Q_UNUSED(port); + Q_UNUSED(connOpts); + if (isOpen()) + close(); + const int32_t err = MimerBeginSession8(db.toUtf8().constData(), user.toUtf8().constData(), + password.toUtf8().constData(), &d->sessionhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not connect to database") + + " "_L1 + db, + err, QSqlError::ConnectionError, nullptr)); + setOpenError(true); + return false; + } + d->dbUser = user; + d->dbName = db; + setOpen(true); + setOpenError(false); + return true; +} + +void QMimerSQLDriver::close() +{ + Q_D(QMimerSQLDriver); + if (isOpen()) { + const int end_err = MimerEndSession(&d->sessionhandle); + if (MIMER_SUCCEEDED(end_err)) { + setOpen(false); + setOpenError(false); + } + } +} + +QSqlResult *QMimerSQLDriver::createResult() const +{ + return new QMimerSQLResult(this); +} + +QStringList QMimerSQLDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + QSqlQuery t(createResult()); + QString sql; + switch (type) { + case QSql::Tables: { + sql = "select table_name from information_schema.tables where " + "table_type=\'BASE TABLE\' AND table_schema = CURRENT_USER"_L1; + break; + } + case QSql::SystemTables: { + sql = "select table_name from information_schema.tables where " + "table_type=\'BASE TABLE\' AND table_schema = \'SYSTEM\'"_L1; + break; + } + case QSql::Views: { + sql = "select table_name from information_schema.tables where " + "table_type=\'VIEW\' AND table_schema = CURRENT_USER"_L1; + break; + } + case QSql::AllTables: { + sql = "select table_name from information_schema.tables where " + "(table_type=\'VIEW\' or table_type=\'BASE TABLE\')" + " AND (table_schema = CURRENT_USER OR table_schema =\'SYSTEM\')"_L1; + break; + } + default: + break; + } + if (sql.length() > 0) { + t.exec(sql); + while (t.next()) + tl.append(t.value(0).toString()); + } + return tl; +} + +QSqlIndex QMimerSQLDriver::primaryIndex(const QString &tablename) const +{ + Q_D(const QMimerSQLDriver); + if (!isOpen()) + return QSqlIndex(); + QString table = tablename; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + QSqlIndex index(tablename); + QSqlQuery t(createResult()); + QString schema; + QString qualifiedName = table; + d->splitTableQualifier(qualifiedName, &schema, &table); + QString sql = + "select information_schema.ext_access_paths.column_name," + "case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type " + "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' " + "when data_type = 'INTEGER' and numeric_precision <= 10 AND NUMERIC_PRECISION > 5 " + "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' " + "else upper(data_type) end as data_type " + "from information_schema.ext_access_paths full outer join " + "information_schema.columns on information_schema.ext_access_paths.column_name = " + "information_schema.columns.column_name and " + "information_schema.ext_access_paths.table_name = " + "information_schema.columns.table_name where " + "information_schema.ext_access_paths.table_name = \'"_L1; + sql.append(table) + .append("\' and index_type = \'PRIMARY KEY\'"_L1); + if (schema.length() == 0) + sql.append(" and table_schema = CURRENT_USER"_L1); + else + sql.append(" and table_schema = \'"_L1).append(schema).append("\'"_L1); + + if (!t.exec(sql)) + return QSqlIndex(); + int i = 0; + while (t.next()) { + QSqlField field(t.value(0).toString(), + QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))), + tablename); + index.insert(i, field); + index.setName(t.value(0).toString()); + i++; + } + return index; +} + +QSqlRecord QMimerSQLDriver::record(const QString &tablename) const +{ + Q_D(const QMimerSQLDriver); + if (!isOpen()) + return QSqlRecord(); + QSqlRecord rec; + QSqlQuery t(createResult()); + QString qualifiedName = tablename; + if (isIdentifierEscaped(qualifiedName, QSqlDriver::TableName)) + qualifiedName = stripDelimiters(qualifiedName, QSqlDriver::TableName); + QString schema, table; + d->splitTableQualifier(qualifiedName, &schema, &table); + + QString sql = + "select column_name, case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type " + "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' " + "when data_type = 'INTEGER' and numeric_precision <= 10 AND numeric_precision > 5 " + "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' " + "else UPPER(data_type) end as data_type, case when is_nullable = 'YES' then false else " + "true end as required, " + "coalesce(numeric_precision, coalesce(datetime_precision,coalesce(interval_precision, " + "-1))) as prec from information_schema.columns where table_name = \'"_L1; + if (schema.length() == 0) + sql.append(table).append("\' and table_schema = CURRENT_USER"_L1); + else + sql.append(table).append("\' and table_schema = \'"_L1).append(schema).append("\'"_L1); + sql.append(" order by ordinal_position"_L1); + if (!t.exec(sql)) + return QSqlRecord(); + + while (t.next()) { + QSqlField field(t.value(0).toString(), + QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))), + tablename); + field.setRequired(t.value(3).toBool()); + if (t.value(3).toInt() != -1) + field.setPrecision(t.value(3).toInt()); + rec.append(field); + } + + return rec; +} + +QVariant QMimerSQLDriver::handle() const +{ + Q_D(const QMimerSQLDriver); + return QVariant::fromValue(d->sessionhandle); +} + +QString QMimerSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + QString res = identifier; + if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) { + res.replace(u'"', "\"\""_L1); + res = u'"' + res + u'"'; + res.replace(u'.', "\".\""_L1); + } + return res; +} + +bool QMimerSQLDriver::beginTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerBeginTransaction(d->sessionhandle, MIMER_TRANS_READWRITE); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not start transaction"), err, + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QMimerSQLDriver::commitTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_COMMIT); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not commit transaction"), err, + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QMimerSQLDriver::rollbackTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_ROLLBACK); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not roll back transaction"), + err, QSqlError::TransactionError, d)); + return false; + } + return true; +} + +void QMimerSQLDriverPrivate::splitTableQualifier(const QString &qualifiedName, QString *schema, + QString *table) const +{ + const QList<QStringView> l = QStringView(qualifiedName).split(u'.'); + int n = l.count(); + if (n > 2) { + return; // can't possibly be a valid table qualifier + } else if (n == 1) { + *schema = QString(); + *table = l.at(0).toString(); + } else { + *schema = l.at(0).toString(); + *table = l.at(1).toString(); + } +} + +QT_END_NAMESPACE + +#include "moc_qsql_mimer.cpp" diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.h b/src/plugins/sqldrivers/mimer/qsql_mimer.h new file mode 100644 index 0000000000..03ad8f21f2 --- /dev/null +++ b/src/plugins/sqldrivers/mimer/qsql_mimer.h @@ -0,0 +1,50 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2022 Mimer Information Technology +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef QSQL_MIMER_H +#define QSQL_MIMER_H + +#include <QtSql/qsqldriver.h> +#include <QUuid> +#include <mimerapi.h> + +#ifdef QT_PLUGIN +# define Q_EXPORT_SQLDRIVER_MIMER +#else +# define Q_EXPORT_SQLDRIVER_MIMER Q_SQL_EXPORT +#endif + +QT_BEGIN_NAMESPACE + +class QMimerSQLDriverPrivate; + +class Q_EXPORT_SQLDRIVER_MIMER QMimerSQLDriver : public QSqlDriver +{ + friend class QMimerSQLResultPrivate; + Q_DECLARE_PRIVATE(QMimerSQLDriver) + Q_OBJECT +public: + explicit QMimerSQLDriver(QObject *parent = nullptr); + explicit QMimerSQLDriver(MimerSession *conn, QObject *parent = nullptr); + ~QMimerSQLDriver() override; + bool hasFeature(DriverFeature f) const override; + bool open(const QString &db, const QString &user, const QString &password, const QString &host, + int port, const QString &connOpts) override; + void close() override; + QSqlResult *createResult() const override; + QStringList tables(QSql::TableType type) const override; + QSqlIndex primaryIndex(const QString &tablename) const override; + QSqlRecord record(const QString &tablename) const override; + QVariant handle() const override; + QString escapeIdentifier(const QString &identifier, IdentifierType type) const override; +protected: + bool beginTransaction() override; + bool commitTransaction() override; + bool rollbackTransaction() override; + +private: +}; + +QT_END_NAMESPACE + +#endif // QSQL_MIMER diff --git a/src/plugins/sqldrivers/mysql/CMakeLists.txt b/src/plugins/sqldrivers/mysql/CMakeLists.txt index a05fc513f1..2e3d028584 100644 --- a/src/plugins/sqldrivers/mysql/CMakeLists.txt +++ b/src/plugins/sqldrivers/mysql/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from mysql.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QMYSQLDriverPlugin Plugin: @@ -13,6 +14,7 @@ qt_internal_add_plugin(QMYSQLDriverPlugin DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES MySQL::MySQL Qt::Core @@ -20,7 +22,4 @@ qt_internal_add_plugin(QMYSQLDriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:mysql.pro:<TRUE>: -# OTHER_FILES = "mysql.json" - qt_internal_force_macos_intel_arch(QMYSQLDriverPlugin) diff --git a/src/plugins/sqldrivers/mysql/main.cpp b/src/plugins/sqldrivers/mysql/main.cpp index f7d56dd1c9..2c10c4d348 100644 --- a/src/plugins/sqldrivers/mysql/main.cpp +++ b/src/plugins/sqldrivers/mysql/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QMYSQLDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,8 +27,7 @@ QMYSQLDriverPlugin::QMYSQLDriverPlugin() QSqlDriver* QMYSQLDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QMYSQL") || - name == QLatin1String("QMARIADB")) { + if (name == "QMYSQL"_L1 || name == "QMARIADB"_L1) { QMYSQLDriver* driver = new QMYSQLDriver(); return driver; } diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp index caf406da46..cfd4931b46 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp +++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -1,56 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 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_mysql_p.h" #include <qcoreapplication.h> #include <qvariant.h> +#include <qvarlengtharray.h> #include <qdatetime.h> #include <qdebug.h> #include <qfile.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qsqlerror.h> #include <qsqlfield.h> #include <qsqlindex.h> #include <qsqlquery.h> #include <qsqlrecord.h> #include <qstringlist.h> +#include <qtimezone.h> #include <QtSql/private/qsqldriver_p.h> #include <QtSql/private/qsqlresult_p.h> @@ -64,12 +31,32 @@ Q_DECLARE_METATYPE(MYSQL_RES*) Q_DECLARE_METATYPE(MYSQL*) Q_DECLARE_METATYPE(MYSQL_STMT*) +// MYSQL_TYPE_JSON was introduced with MySQL 5.7.9 +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID < 50709 +#define MYSQL_TYPE_JSON 245 +#endif + // MySQL above version 8 removed my_bool typedef while MariaDB kept it, // by redefining it we can regain source compatibility. using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr)); +// this is a copy of the old MYSQL_TIME before an additional integer was added in +// 8.0.27.0. This kills the sanity check during retrieving this struct from mysql +// when another libmysql version is used during runtime than during compile time +struct QT_MYSQL_TIME +{ + unsigned int year, month, day, hour, minute, second; + unsigned long second_part; /**< microseconds */ + my_bool neg; + enum enum_mysql_timestamp_type time_type; +}; + QT_BEGIN_NAMESPACE +static Q_LOGGING_CATEGORY(lcMysql, "qt.sql.mysql") + +using namespace Qt::StringLiterals; + class QMYSQLDriverPrivate : public QSqlDriverPrivate { Q_DECLARE_PUBLIC(QMYSQLDriver) @@ -78,6 +65,7 @@ public: QMYSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MySqlServer) {} MYSQL *mysql = nullptr; + QString dbName; bool preparedQuerysEnabled = false; }; @@ -113,10 +101,15 @@ static inline QVariant qDateTimeFromString(QString &val) #else if (val.isEmpty()) return QVariant(QDateTime()); - if (val.length() == 14) - // TIMESTAMPS have the format yyyyMMddhhmmss - val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10, - QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':')); + + // TIMESTAMPS have either the format "yyyyMMddhhmmss" or "yyyy-MM-dd + // hh:mm:ss". QDateTime::fromString() can convert the latter, but not the + // former, so adapt it if necessary. + if (val.size() == 14) + val.insert(4, u'-').insert(7, u'-').insert(10, u'T').insert(13, u':').insert(16, u':'); + + if (!val.endsWith(u'Z')) + val.append(u'Z'); // make UTC return QVariant(QDateTime::fromString(val, Qt::ISODate)); #endif } @@ -135,6 +128,18 @@ static inline bool checkPreparedQueries(MYSQL *mysql) return mysql_stmt_param_count(stmt.get()) == 2; } +// used with prepared queries and bound arguments +static inline void setUtcTimeZone(MYSQL *mysql) +{ + std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close); + if (!stmt) + return; + + static const char query[] = "SET time_zone = '+00:00'"; + if (mysql_stmt_prepare(stmt.get(), query, sizeof(query) - 1)) + mysql_stmt_execute(stmt.get()); +} + class QMYSQLResultPrivate; class QMYSQLResult : public QSqlResult @@ -205,68 +210,70 @@ public: bool preparedQuery = false; }; -static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, - const QMYSQLDriverPrivate* p) +static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, + const QMYSQLDriverPrivate *p) { - const char *cerr = p->mysql ? mysql_error(p->mysql) : 0; - return QSqlError(QLatin1String("QMYSQL: ") + err, + const char *cerr = p->mysql ? mysql_error(p->mysql) : nullptr; + return QSqlError("QMYSQL: "_L1 + err, QString::fromUtf8(cerr), type, QString::number(mysql_errno(p->mysql))); } -static QMetaType qDecodeMYSQLType(int mysqltype, uint flags) +static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags) { QMetaType::Type type; switch (mysqltype) { - case FIELD_TYPE_TINY : + case MYSQL_TYPE_TINY: type = (flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char; break; - case FIELD_TYPE_SHORT : + case MYSQL_TYPE_SHORT: type = (flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short; break; - case FIELD_TYPE_LONG : - case FIELD_TYPE_INT24 : + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: type = (flags & UNSIGNED_FLAG) ? QMetaType::UInt : QMetaType::Int; break; - case FIELD_TYPE_YEAR : + case MYSQL_TYPE_YEAR: type = QMetaType::Int; break; - case FIELD_TYPE_LONGLONG : + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_LONGLONG: type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong; break; - case FIELD_TYPE_FLOAT : - case FIELD_TYPE_DOUBLE : - case FIELD_TYPE_DECIMAL : -#if defined(FIELD_TYPE_NEWDECIMAL) - case FIELD_TYPE_NEWDECIMAL: -#endif + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: type = QMetaType::Double; break; - case FIELD_TYPE_DATE : + case MYSQL_TYPE_DATE: type = QMetaType::QDate; break; - case FIELD_TYPE_TIME : + case MYSQL_TYPE_TIME: // A time field can be within the range '-838:59:59' to '838:59:59' so // use QString instead of QTime since QTime is limited to 24 hour clock type = QMetaType::QString; break; - case FIELD_TYPE_DATETIME : - case FIELD_TYPE_TIMESTAMP : + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: type = QMetaType::QDateTime; break; - case FIELD_TYPE_STRING : - case FIELD_TYPE_VAR_STRING : - case FIELD_TYPE_BLOB : - case FIELD_TYPE_TINY_BLOB : - case FIELD_TYPE_MEDIUM_BLOB : - case FIELD_TYPE_LONG_BLOB : - case FIELD_TYPE_GEOMETRY : + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_JSON: type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString; break; - default: - case FIELD_TYPE_ENUM : - case FIELD_TYPE_SET : + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + type = QMetaType::QString; + break; + default: // needed because there are more enum values which are not available in all headers type = QMetaType::QString; break; } @@ -276,34 +283,34 @@ static QMetaType qDecodeMYSQLType(int mysqltype, uint flags) static QSqlField qToField(MYSQL_FIELD *field) { QSqlField f(QString::fromUtf8(field->name), - qDecodeMYSQLType(int(field->type), field->flags), + qDecodeMYSQLType(field->type, field->flags), QString::fromUtf8(field->table)); f.setRequired(IS_NOT_NULL(field->flags)); f.setLength(field->length); f.setPrecision(field->decimals); - f.setSqlType(field->type); f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG); return f; } -static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type, - MYSQL_STMT* stmt) +static QSqlError qMakeStmtError(const QString &err, QSqlError::ErrorType type, + MYSQL_STMT *stmt) { const char *cerr = mysql_stmt_error(stmt); - return QSqlError(QLatin1String("QMYSQL: ") + err, + return QSqlError("QMYSQL: "_L1 + err, QString::fromLatin1(cerr), type, QString::number(mysql_stmt_errno(stmt))); } -static bool qIsBlob(int t) +static bool qIsBlob(enum_field_types t) { return t == MYSQL_TYPE_TINY_BLOB || t == MYSQL_TYPE_BLOB || t == MYSQL_TYPE_MEDIUM_BLOB - || t == MYSQL_TYPE_LONG_BLOB; + || t == MYSQL_TYPE_LONG_BLOB + || t == MYSQL_TYPE_JSON; } -static bool qIsTimeOrDate(int t) +static bool qIsTimeOrDate(enum_field_types t) { // *not* MYSQL_TYPE_TIME because its range is bigger than QTime // (see above) @@ -318,9 +325,14 @@ static bool qIsInteger(int t) || t == QMetaType::LongLong || t == QMetaType::ULongLong; } +static inline bool qIsBitfield(enum_field_types type) +{ + return type == MYSQL_TYPE_BIT; +} + void QMYSQLResultPrivate::bindBlobs() { - for (int i = 0; i < fields.count(); ++i) { + for (int i = 0; i < fields.size(); ++i) { const MYSQL_FIELD *fieldInfo = fields.at(i).myField; if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) { MYSQL_BIND *bind = &inBinds[i]; @@ -362,7 +374,7 @@ bool QMYSQLResultPrivate::bindInValues() bind->buffer_length = f.bufLength = 0; hasBlobs = true; } else if (qIsTimeOrDate(fieldInfo->type)) { - bind->buffer_length = f.bufLength = sizeof(MYSQL_TIME); + bind->buffer_length = f.bufLength = sizeof(QT_MYSQL_TIME); } else if (qIsInteger(f.type.id())) { bind->buffer_length = f.bufLength = 8; } else { @@ -375,8 +387,6 @@ bool QMYSQLResultPrivate::bindInValues() char *field = bind->buffer_length ? new char[bind->buffer_length + 1]{} : nullptr; bind->buffer = f.outField = field; - if (qIsTimeOrDate(fieldInfo->type)) - new (field) MYSQL_TIME; ++i; } @@ -408,7 +418,7 @@ void QMYSQLResult::cleanup() if (d->result) mysql_free_result(d->result); -// must iterate trough leftover result sets from multi-selects or stored procedures +// must iterate through leftover result sets from multi-selects or stored procedures // if this isn't done subsequent queries will fail with "Commands out of sync" while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) { MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql); @@ -418,7 +428,7 @@ void QMYSQLResult::cleanup() if (d->stmt) { if (mysql_stmt_close(d->stmt)) - qWarning("QMYSQLResult::cleanup: unable to free statement handle"); + qCWarning(lcMysql, "QMYSQLResult::cleanup: unable to free statement handle"); d->stmt = 0; } @@ -427,11 +437,8 @@ void QMYSQLResult::cleanup() d->meta = 0; } - for (const auto &field : qAsConst(d->fields)) { - if (qIsTimeOrDate(field.myField->type)) - reinterpret_cast<MYSQL_TIME *>(field.outField)->~MYSQL_TIME(); - delete[] field.outField; - } + for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields)) + delete[] f.outField; if (d->outBinds) { delete[] d->outBinds; @@ -521,12 +528,7 @@ bool QMYSQLResult::fetchLast() return success; } - my_ulonglong numRows; - if (d->preparedQuery) { - numRows = mysql_stmt_num_rows(d->stmt); - } else { - numRows = mysql_num_rows(d->result); - } + my_ulonglong numRows = d->preparedQuery ? mysql_stmt_num_rows(d->stmt) : mysql_num_rows(d->result); if (at() == int(numRows)) return true; if (!numRows) @@ -544,11 +546,25 @@ bool QMYSQLResult::fetchFirst() return fetch(0); } +static inline uint64_t +qDecodeBitfield(const QMYSQLResultPrivate::QMyField &f, const char *outField) +{ + // byte-aligned length + const auto numBytes = (f.myField->length + 7) / 8; + uint64_t val = 0; + for (unsigned long i = 0; i < numBytes && outField; ++i) { + uint64_t tmp = static_cast<uint8_t>(outField[i]); + val <<= 8; + val |= tmp; + } + return val; +} + QVariant QMYSQLResult::data(int field) { Q_D(QMYSQLResult); - if (!isSelect() || field >= d->fields.count()) { - qWarning("QMYSQLResult::data: column %d out of range", field); + if (!isSelect() || field >= d->fields.size()) { + qCWarning(lcMysql, "QMYSQLResult::data: column %d out of range", field); return QVariant(); } @@ -561,8 +577,9 @@ QVariant QMYSQLResult::data(int field) if (d->preparedQuery) { if (f.nullIndicator) return QVariant(f.type); - - if (qIsInteger(f.type.id())) { + if (qIsBitfield(f.myField->type)) { + return QVariant::fromValue(qDecodeBitfield(f, f.outField)); + } else if (qIsInteger(f.type.id())) { QVariant variant(f.type, f.outField); // we never want to return char variants here, see QTBUG-53397 if (f.type.id() == QMetaType::UChar) @@ -570,8 +587,8 @@ QVariant QMYSQLResult::data(int field) else if (f.type.id() == QMetaType::Char) return variant.toInt(); return variant; - } else if (qIsTimeOrDate(f.myField->type) && f.bufLength == sizeof(MYSQL_TIME)) { - auto t = reinterpret_cast<const MYSQL_TIME *>(f.outField); + } else if (qIsTimeOrDate(f.myField->type) && f.bufLength >= sizeof(QT_MYSQL_TIME)) { + auto t = reinterpret_cast<const QT_MYSQL_TIME *>(f.outField); QDate date; QTime time; if (f.type.id() != QMetaType::QTime) @@ -579,7 +596,7 @@ QVariant QMYSQLResult::data(int field) if (f.type.id() != QMetaType::QDate) time = QTime(t->hour, t->minute, t->second, t->second_part / 1000); if (f.type.id() == QMetaType::QDateTime) - return QDateTime(date, time); + return QDateTime(date, time, QTimeZone::UTC); else if (f.type.id() == QMetaType::QDate) return date; else @@ -594,6 +611,9 @@ QVariant QMYSQLResult::data(int field) return QVariant(f.type); } + if (qIsBitfield(f.myField->type)) + return QVariant::fromValue(qDecodeBitfield(f, d->row[field])); + fieldLength = mysql_fetch_lengths(d->result)[field]; if (f.type.id() != QMetaType::QByteArray) @@ -663,7 +683,7 @@ QVariant QMYSQLResult::data(int field) bool QMYSQLResult::isNull(int field) { Q_D(const QMYSQLResult); - if (field < 0 || field >= d->fields.count()) + if (field < 0 || field >= d->fields.size()) return true; if (d->preparedQuery) return d->fields.at(field).nullIndicator; @@ -682,7 +702,7 @@ bool QMYSQLResult::reset (const QString& query) cleanup(); const QByteArray encQuery = query.toUtf8(); - if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) { + if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.size())) { setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"), QSqlError::StatementError, d->drv_d_func())); return false; @@ -702,6 +722,7 @@ bool QMYSQLResult::reset (const QString& query) for(int i = 0; i < numFields; i++) { MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + d->fields[i].myField = field; } setAt(QSql::BeforeFirstRow); } @@ -767,7 +788,7 @@ QSqlRecord QMYSQLResult::record() const if (!mysql_errno(d->drv_d_func()->mysql)) { mysql_field_seek(res, 0); MYSQL_FIELD* field = mysql_fetch_field(res); - while(field) { + while (field) { info.append(qToField(field)); field = mysql_fetch_field(res); } @@ -790,8 +811,8 @@ bool QMYSQLResult::nextResult() d->result = 0; setSelect(false); - for (int i = 0; i < d->fields.count(); ++i) - delete[] d->fields[i].outField; + for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields)) + delete[] f.outField; d->fields.clear(); int status = mysql_next_result(d->drv_d_func()->mysql); @@ -804,7 +825,7 @@ bool QMYSQLResult::nextResult() } d->result = mysql_store_result(d->drv_d_func()->mysql); - int numFields = mysql_field_count(d->drv_d_func()->mysql); + unsigned int numFields = mysql_field_count(d->drv_d_func()->mysql); if (!d->result && numFields > 0) { setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"), QSqlError::StatementError, d->drv_d_func())); @@ -816,9 +837,10 @@ bool QMYSQLResult::nextResult() d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql); if (isSelect()) { - for (int i = 0; i < numFields; i++) { - MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + for (unsigned int i = 0; i < numFields; i++) { + MYSQL_FIELD *field = mysql_fetch_field_direct(d->result, i); d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + d->fields[i].myField = field; } } @@ -831,29 +853,6 @@ void QMYSQLResult::virtual_hook(int id, void *data) QSqlResult::virtual_hook(id, data); } -static MYSQL_TIME *toMySqlDate(QDate date, QTime time, int type) -{ - Q_ASSERT(type == QMetaType::QTime || type == QMetaType::QDate - || type == QMetaType::QDateTime); - - MYSQL_TIME *myTime = new MYSQL_TIME; - memset(myTime, 0, sizeof(MYSQL_TIME)); - - if (type == QMetaType::QTime || type == QMetaType::QDateTime) { - myTime->hour = time.hour(); - myTime->minute = time.minute(); - myTime->second = time.second(); - myTime->second_part = time.msec() * 1000; - } - if (type == QMetaType::QDate || type == QMetaType::QDateTime) { - myTime->year = date.year(); - myTime->month = date.month(); - myTime->day = date.day(); - } - - return myTime; -} - bool QMYSQLResult::prepare(const QString& query) { Q_D(QMYSQLResult); @@ -878,7 +877,7 @@ bool QMYSQLResult::prepare(const QString& query) } const QByteArray encQuery = query.toUtf8(); - r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length()); + r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.size()); if (r != 0) { setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"), QSqlError::StatementError, d->stmt)); @@ -886,9 +885,9 @@ bool QMYSQLResult::prepare(const QString& query) return false; } - if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues - d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)]; - } + const auto paramCount = mysql_stmt_param_count(d->stmt); + if (paramCount > 0) // allocate memory for outvalues + d->outBinds = new MYSQL_BIND[paramCount](); setSelect(d->bindInValues()); d->preparedQuery = true; @@ -906,7 +905,7 @@ bool QMYSQLResult::exec() return false; int r = 0; - QList<MYSQL_TIME *> timeVector; + QList<QT_MYSQL_TIME *> timeVector; QList<QByteArray> stringVector; QList<my_bool> nullVector; @@ -919,17 +918,16 @@ bool QMYSQLResult::exec() return false; } - if (mysql_stmt_param_count(d->stmt) > 0 && - mysql_stmt_param_count(d->stmt) == (uint)values.count()) { - - nullVector.resize(values.count()); - for (int i = 0; i < values.count(); ++i) { + const unsigned long paramCount = mysql_stmt_param_count(d->stmt); + if (paramCount > 0 && paramCount == static_cast<size_t>(values.size())) { + nullVector.resize(values.size()); + for (qsizetype i = 0; i < values.size(); ++i) { const QVariant &val = boundValues().at(i); void *data = const_cast<void *>(val.constData()); MYSQL_BIND* currBind = &d->outBinds[i]; - nullVector[i] = static_cast<my_bool>(val.isNull()); + nullVector[i] = static_cast<my_bool>(QSqlResultPrivate::isVariantNull(val)); currBind->is_null = &nullVector[i]; currBind->length = 0; currBind->is_unsigned = 0; @@ -944,27 +942,41 @@ bool QMYSQLResult::exec() case QMetaType::QTime: case QMetaType::QDate: case QMetaType::QDateTime: { - MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.userType()); + auto myTime = new QT_MYSQL_TIME{}; timeVector.append(myTime); - currBind->buffer = myTime; - switch (val.userType()) { - case QMetaType::QTime: + + QDate date; + QTime time; + int type = val.userType(); + if (type == QMetaType::QTime) { + time = val.toTime(); currBind->buffer_type = MYSQL_TYPE_TIME; myTime->time_type = MYSQL_TIMESTAMP_TIME; - break; - case QMetaType::QDate: + } else if (type == QMetaType::QDate) { + date = val.toDate(); currBind->buffer_type = MYSQL_TYPE_DATE; myTime->time_type = MYSQL_TIMESTAMP_DATE; - break; - case QMetaType::QDateTime: + } else { + QDateTime dt = val.toDateTime().toUTC(); + date = dt.date(); + time = dt.time(); currBind->buffer_type = MYSQL_TYPE_DATETIME; myTime->time_type = MYSQL_TIMESTAMP_DATETIME; - break; - default: - break; } - currBind->buffer_length = sizeof(MYSQL_TIME); + + if (type == QMetaType::QTime || type == QMetaType::QDateTime) { + myTime->hour = time.hour(); + myTime->minute = time.minute(); + myTime->second = time.second(); + myTime->second_part = time.msec() * 1000; + } + if (type == QMetaType::QDate || type == QMetaType::QDateTime) { + myTime->year = date.year(); + myTime->month = date.month(); + myTime->day = date.day(); + } + currBind->buffer_length = sizeof(QT_MYSQL_TIME); currBind->length = 0; break; } case QMetaType::UInt: @@ -998,12 +1010,16 @@ bool QMYSQLResult::exec() stringVector.append(ba); currBind->buffer_type = MYSQL_TYPE_STRING; currBind->buffer = const_cast<char *>(ba.constData()); - currBind->buffer_length = ba.length(); + currBind->buffer_length = ba.size(); break; } } } +#if defined(MARIADB_VERSION_ID) || MYSQL_VERSION_ID < 80300 r = mysql_stmt_bind_param(d->stmt, d->outBinds); +#else + r = mysql_stmt_bind_named_param(d->stmt, d->outBinds, paramCount, nullptr); +#endif if (r != 0) { setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", "Unable to bind value"), QSqlError::StatementError, d->stmt)); @@ -1074,7 +1090,7 @@ static void qLibraryInit() return; if (mysql_library_init(0, 0, 0)) { - qWarning("QMYSQLDriver::qServerInit: unable to start server."); + qCWarning(lcMysql, "QMYSQLDriver::qServerInit: unable to start server."); } #endif // Q_NO_MYSQL_EMBEDDED @@ -1166,100 +1182,182 @@ bool QMYSQLDriver::hasFeature(DriverFeature f) const return false; } -static void setOptionFlag(uint &optionFlags, const QString &opt) +static void setOptionFlag(uint &optionFlags, QStringView opt) { - if (opt == QLatin1String("CLIENT_COMPRESS")) + if (opt == "CLIENT_COMPRESS"_L1) optionFlags |= CLIENT_COMPRESS; - else if (opt == QLatin1String("CLIENT_FOUND_ROWS")) + else if (opt == "CLIENT_FOUND_ROWS"_L1) optionFlags |= CLIENT_FOUND_ROWS; - else if (opt == QLatin1String("CLIENT_IGNORE_SPACE")) + else if (opt == "CLIENT_IGNORE_SPACE"_L1) optionFlags |= CLIENT_IGNORE_SPACE; - else if (opt == QLatin1String("CLIENT_INTERACTIVE")) + else if (opt == "CLIENT_INTERACTIVE"_L1) optionFlags |= CLIENT_INTERACTIVE; - else if (opt == QLatin1String("CLIENT_NO_SCHEMA")) + else if (opt == "CLIENT_NO_SCHEMA"_L1) optionFlags |= CLIENT_NO_SCHEMA; - else if (opt == QLatin1String("CLIENT_ODBC")) + else if (opt == "CLIENT_ODBC"_L1) optionFlags |= CLIENT_ODBC; - else if (opt == QLatin1String("CLIENT_SSL")) - qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL."); + else if (opt == "CLIENT_SSL"_L1) + qCWarning(lcMysql, "QMYSQLDriver: MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT " + "and MYSQL_OPT_SSL_CA should be used instead of CLIENT_SSL."); + else + qCWarning(lcMysql, "QMYSQLDriver::open: Unknown connect option '%ls'", + qUtf16Printable(QString(opt))); +} + +static bool setOptionString(MYSQL *mysql, mysql_option option, QStringView v) +{ + return mysql_options(mysql, option, v.toUtf8().constData()) == 0; +} + +static bool setOptionInt(MYSQL *mysql, mysql_option option, QStringView v) +{ + bool bOk; + const auto val = v.toInt(&bOk); + return bOk ? mysql_options(mysql, option, &val) == 0 : false; +} + +static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v) +{ + bool val = (v.isEmpty() || v == "TRUE"_L1 || v == "1"_L1); + return mysql_options(mysql, option, &val) == 0; +} + +// MYSQL_OPT_SSL_MODE was introduced with MySQL 5.7.11 +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID) +static bool setOptionSslMode(MYSQL *mysql, mysql_option option, QStringView v) +{ + mysql_ssl_mode sslMode = SSL_MODE_DISABLED; + if (v == "DISABLED"_L1 || v == "SSL_MODE_DISABLED"_L1) + sslMode = SSL_MODE_DISABLED; + else if (v == "PREFERRED"_L1 || v == "SSL_MODE_PREFERRED"_L1) + sslMode = SSL_MODE_PREFERRED; + else if (v == "REQUIRED"_L1 || v == "SSL_MODE_REQUIRED"_L1) + sslMode = SSL_MODE_REQUIRED; + else if (v == "VERIFY_CA"_L1 || v == "SSL_MODE_VERIFY_CA"_L1) + sslMode = SSL_MODE_VERIFY_CA; + else if (v == "VERIFY_IDENTITY"_L1 || v == "SSL_MODE_VERIFY_IDENTITY"_L1) + sslMode = SSL_MODE_VERIFY_IDENTITY; + else + qCWarning(lcMysql, "Unknown ssl mode '%ls' - using SSL_MODE_DISABLED", + qUtf16Printable(QString(v))); + return mysql_options(mysql, option, &sslMode) == 0; +} +#endif + +static bool setOptionProtocol(MYSQL *mysql, mysql_option option, QStringView v) +{ + mysql_protocol_type proto = MYSQL_PROTOCOL_DEFAULT; + if (v == "TCP"_L1 || v == "MYSQL_PROTOCOL_TCP"_L1) + proto = MYSQL_PROTOCOL_TCP; + else if (v == "SOCKET"_L1 || v == "MYSQL_PROTOCOL_SOCKET"_L1) + proto = MYSQL_PROTOCOL_SOCKET; + else if (v == "PIPE"_L1 || v == "MYSQL_PROTOCOL_PIPE"_L1) + proto = MYSQL_PROTOCOL_PIPE; + else if (v == "MEMORY"_L1 || v == "MYSQL_PROTOCOL_MEMORY"_L1) + proto = MYSQL_PROTOCOL_MEMORY; + else if (v == "DEFAULT"_L1 || v == "MYSQL_PROTOCOL_DEFAULT"_L1) + proto = MYSQL_PROTOCOL_DEFAULT; else - qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData()); + qCWarning(lcMysql, "Unknown protocol '%ls' - using MYSQL_PROTOCOL_DEFAULT", + qUtf16Printable(QString(v))); + return mysql_options(mysql, option, &proto) == 0; } -bool QMYSQLDriver::open(const QString& db, - const QString& user, - const QString& password, - const QString& host, - int port, - const QString& connOpts) +bool QMYSQLDriver::open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) { Q_D(QMYSQLDriver); if (isOpen()) close(); + if (!(d->mysql = mysql_init(nullptr))) { + setLastError(qMakeError(tr("Unable to allocate a MYSQL object"), + QSqlError::ConnectionError, d)); + setOpenError(true); + return false; + } + + typedef bool (*SetOptionFunc)(MYSQL*, mysql_option, QStringView); + struct mysqloptions { + QLatin1StringView key; + mysql_option option; + SetOptionFunc func; + }; + const mysqloptions options[] = { + {"SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString}, + {"SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString}, + {"SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString}, + {"SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString}, + {"SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString}, + {"MYSQL_OPT_SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString}, + {"MYSQL_OPT_SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString}, + {"MYSQL_OPT_SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString}, + {"MYSQL_OPT_SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString}, + {"MYSQL_OPT_SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString}, + {"MYSQL_OPT_SSL_CRL"_L1, MYSQL_OPT_SSL_CRL, setOptionString}, + {"MYSQL_OPT_SSL_CRLPATH"_L1, MYSQL_OPT_SSL_CRLPATH, setOptionString}, +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710 + {"MYSQL_OPT_TLS_VERSION"_L1, MYSQL_OPT_TLS_VERSION, setOptionString}, +#endif +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID) + {"MYSQL_OPT_SSL_MODE"_L1, MYSQL_OPT_SSL_MODE, setOptionSslMode}, +#endif + {"MYSQL_OPT_CONNECT_TIMEOUT"_L1, MYSQL_OPT_CONNECT_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_READ_TIMEOUT"_L1, MYSQL_OPT_READ_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_WRITE_TIMEOUT"_L1, MYSQL_OPT_WRITE_TIMEOUT, setOptionInt}, + {"MYSQL_OPT_RECONNECT"_L1, MYSQL_OPT_RECONNECT, setOptionBool}, + {"MYSQL_OPT_LOCAL_INFILE"_L1, MYSQL_OPT_LOCAL_INFILE, setOptionInt}, + {"MYSQL_OPT_PROTOCOL"_L1, MYSQL_OPT_PROTOCOL, setOptionProtocol}, + {"MYSQL_SHARED_MEMORY_BASE_NAME"_L1, MYSQL_SHARED_MEMORY_BASE_NAME, setOptionString}, + }; + auto trySetOption = [&](const QStringView &key, const QStringView &value) -> bool { + for (const mysqloptions &opt : options) { + if (key == opt.key) { + if (!opt.func(d->mysql, opt.option, value)) { + qCWarning(lcMysql, "QMYSQLDriver::open: Could not set connect option value " + "'%ls' to '%ls'", + qUtf16Printable(QString(key)), qUtf16Printable(QString(value))); + } + return true; + } + } + return false; + }; + /* This is a hack to get MySQL's stored procedure support working. Since a stored procedure _may_ return multiple result sets, we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_ stored procedure call will fail. */ unsigned int optionFlags = CLIENT_MULTI_STATEMENTS; - const QStringList opts(connOpts.split(QLatin1Char(';'), Qt::SkipEmptyParts)); + const QList<QStringView> opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts)); QString unixSocket; - QString sslCert; - QString sslCA; - QString sslKey; - QString sslCAPath; - QString sslCipher; - my_bool reconnect=false; - uint connectTimeout = 0; - uint readTimeout = 0; - uint writeTimeout = 0; // extract the real options from the string - for (int i = 0; i < opts.count(); ++i) { - QString tmp(opts.at(i).simplified()); - int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { - QString val = tmp.mid(idx + 1).simplified(); - QString opt = tmp.left(idx).simplified(); - if (opt == QLatin1String("UNIX_SOCKET")) - unixSocket = val; - else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) { - if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty()) - reconnect = true; - } else if (opt == QLatin1String("MYSQL_OPT_CONNECT_TIMEOUT")) - connectTimeout = val.toInt(); - else if (opt == QLatin1String("MYSQL_OPT_READ_TIMEOUT")) - readTimeout = val.toInt(); - else if (opt == QLatin1String("MYSQL_OPT_WRITE_TIMEOUT")) - writeTimeout = val.toInt(); - else if (opt == QLatin1String("SSL_KEY")) - sslKey = val; - else if (opt == QLatin1String("SSL_CERT")) - sslCert = val; - else if (opt == QLatin1String("SSL_CA")) - sslCA = val; - else if (opt == QLatin1String("SSL_CAPATH")) - sslCAPath = val; - else if (opt == QLatin1String("SSL_CIPHER")) - sslCipher = val; - else if (val == QLatin1String("TRUE") || val == QLatin1String("1")) - setOptionFlag(optionFlags, tmp.left(idx).simplified()); + for (const auto &option : opts) { + const QStringView sv = QStringView(option).trimmed(); + qsizetype idx; + if ((idx = sv.indexOf(u'=')) != -1) { + const QStringView key = sv.left(idx).trimmed(); + const QStringView val = sv.mid(idx + 1).trimmed(); + if (trySetOption(key, val)) + continue; + else if (key == "UNIX_SOCKET"_L1) + unixSocket = val.toString(); + else if (val == "TRUE"_L1 || val == "1"_L1) + setOptionFlag(optionFlags, key); else - qWarning("QMYSQLDriver::open: Illegal connect option value '%s'", - tmp.toLocal8Bit().constData()); + qCWarning(lcMysql, "QMYSQLDriver::open: Illegal connect option value '%ls'", + qUtf16Printable(QString(sv))); } else { - setOptionFlag(optionFlags, tmp); + setOptionFlag(optionFlags, sv); } } - if (!(d->mysql = mysql_init(nullptr))) { - setLastError(qMakeError(tr("Unable to allocate a MYSQL object"), - QSqlError::ConnectionError, d)); - setOpenError(true); - return false; - } - // try utf8 with non BMP first, utf8 (BMP only) if that fails static const char wanted_charsets[][8] = { "utf8mb4", "utf8" }; #ifdef MARIADB_VERSION_ID @@ -1278,23 +1376,6 @@ bool QMYSQLDriver::open(const QString& db, } *cs = nullptr; #endif - if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() || - !sslCAPath.isNull() || !sslCipher.isNull()) { - mysql_ssl_set(d->mysql, - sslKey.isNull() ? nullptr : sslKey.toUtf8().constData(), - sslCert.isNull() ? nullptr : sslCert.toUtf8().constData(), - sslCA.isNull() ? nullptr : sslCA.toUtf8().constData(), - sslCAPath.isNull() ? nullptr : sslCAPath.toUtf8().constData(), - sslCipher.isNull() ? nullptr : sslCipher.toUtf8().constData()); - } - - if (connectTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout); - if (readTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout); - if (writeTimeout != 0) - mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout); - MYSQL *mysql = mysql_real_connect(d->mysql, host.isNull() ? nullptr : host.toUtf8().constData(), user.isNull() ? nullptr : user.toUtf8().constData(), @@ -1323,9 +1404,10 @@ bool QMYSQLDriver::open(const QString& db, } } if (!ok) - qWarning("MySQL: Unable to set the client character set to utf8 (\"%s\"). Using '%s' instead.", - mysql_error(d->mysql), - mysql_character_set_name(d->mysql)); + qCWarning(lcMysql, "MySQL: Unable to set the client character set to utf8 (\"%s\"). " + "Using '%s' instead.", + mysql_error(d->mysql), + mysql_character_set_name(d->mysql)); } if (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) { @@ -1335,10 +1417,11 @@ bool QMYSQLDriver::open(const QString& db, return false; } - if (reconnect) - mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect); - d->preparedQuerysEnabled = checkPreparedQueries(d->mysql); + d->dbName = db; + + if (d->preparedQuerysEnabled) + setUtcTimeZone(d->mysql); #if QT_CONFIG(thread) mysql_thread_init(); @@ -1358,6 +1441,7 @@ void QMYSQLDriver::close() #endif mysql_close(d->mysql); d->mysql = nullptr; + d->dbName.clear(); setOpen(false); setOpenError(false); } @@ -1374,14 +1458,14 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const QStringList tl; QSqlQuery q(createResult()); if (type & QSql::Tables) { - QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'BASE TABLE'"); + QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'BASE TABLE'"_L1; q.exec(sql); while (q.next()) tl.append(q.value(0).toString()); } if (type & QSql::Views) { - QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'VIEW'"); + QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'VIEW'"_L1; q.exec(sql); while (q.next()) @@ -1390,18 +1474,18 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const return tl; } -QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const +QSqlIndex QMYSQLDriver::primaryIndex(const QString &tablename) const { QSqlIndex idx; if (!isOpen()) return idx; QSqlQuery i(createResult()); - QString stmt(QLatin1String("show index from %1;")); + QString stmt("show index from %1;"_L1); QSqlRecord fil = record(tablename); i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName))); while (i.isActive() && i.next()) { - if (i.value(2).toString() == QLatin1String("PRIMARY")) { + if (i.value(2).toString() == "PRIMARY"_L1) { idx.append(fil.field(i.value(4).toString())); idx.setCursorName(i.value(0).toString()); idx.setName(i.value(2).toString()); @@ -1411,26 +1495,41 @@ QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const return idx; } -QSqlRecord QMYSQLDriver::record(const QString& tablename) const +QSqlRecord QMYSQLDriver::record(const QString &tablename) const { Q_D(const QMYSQLDriver); - QString table=tablename; - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - - QSqlRecord info; if (!isOpen()) - return info; - MYSQL_RES* r = mysql_list_fields(d->mysql, table.toUtf8().constData(), 0); - if (!r) { - return info; + return {}; + QSqlQuery i(createResult()); + QString stmt("SELECT * FROM %1 LIMIT 0"_L1); + i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName))); + auto r = i.record(); + if (r.isEmpty()) + return r; + // no binding of WHERE possible with MySQL + // escaping on WHERE clause does not work, so use mysql_real_escape_string() + stmt = "SELECT column_name, column_default FROM information_schema.columns WHERE table_name = '%1'"_L1; + const auto baTableName = tablename.toUtf8(); + QVarLengthArray<char> tableNameQuoted(baTableName.size() * 2 + 1); +#if defined(MARIADB_VERSION_ID) + const auto len = mysql_real_escape_string(d->mysql, tableNameQuoted.data(), + baTableName.data(), baTableName.size()); +#else + const auto len = mysql_real_escape_string_quote(d->mysql, tableNameQuoted.data(), + baTableName.data(), baTableName.size(), '\''); +#endif + if (i.exec(stmt.arg(QString::fromUtf8(tableNameQuoted.data(), len)))) { + while (i.next()) { + const auto colName = i.value(0).toString(); + const auto recordIdx = r.indexOf(colName); + if (recordIdx >= 0) { + auto field = r.field(recordIdx); + field.setDefaultValue(i.value(1)); + r.replace(recordIdx, field); + } + } } - MYSQL_FIELD* field; - - while ((field = mysql_fetch_field(r))) - info.append(qToField(field)); - mysql_free_result(r); - return info; + return r; } QVariant QMYSQLDriver::handle() const @@ -1443,7 +1542,7 @@ bool QMYSQLDriver::beginTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::beginTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::beginTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "BEGIN WORK")) { @@ -1458,7 +1557,7 @@ bool QMYSQLDriver::commitTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::commitTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::commitTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "COMMIT")) { @@ -1473,7 +1572,7 @@ bool QMYSQLDriver::rollbackTransaction() { Q_D(QMYSQLDriver); if (!isOpen()) { - qWarning("QMYSQLDriver::rollbackTransaction: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::rollbackTransaction: Database not open"); return false; } if (mysql_query(d->mysql, "ROLLBACK")) { @@ -1498,7 +1597,7 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons case QMetaType::QString: // Escape '\' characters r = QSqlDriver::formatValue(field, trimStrings); - r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + r.replace("\\"_L1, "\\\\"_L1); break; case QMetaType::QByteArray: if (isOpen()) { @@ -1507,10 +1606,10 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons QVarLengthArray<char, 512> buffer(ba.size() * 2 + 1); auto escapedSize = mysql_real_escape_string(d->mysql, buffer.data(), ba.data(), ba.size()); r.reserve(escapedSize + 3); - r = QLatin1Char('\'') + QString::fromUtf8(buffer) + QLatin1Char('\''); + r = u'\'' + QString::fromUtf8(buffer.data(), escapedSize) + u'\''; break; } else { - qWarning("QMYSQLDriver::formatValue: Database not open"); + qCWarning(lcMysql, "QMYSQLDriver::formatValue: Database not open"); } Q_FALLTHROUGH(); case QMetaType::QDateTime: @@ -1519,12 +1618,11 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons // "+00:00" starting in version 8.0.19. However, if we got here, // it's because the MySQL server is too old for prepared queries // in the first place, so it won't understand timezones either. - // Besides, MYSQL_TIME does not support timezones, so match it. - r = QLatin1Char('\'') + + r = u'\'' + dt.date().toString(Qt::ISODate) + - QLatin1Char('T') + + u'T' + dt.time().toString(Qt::ISODate) + - QLatin1Char('\''); + u'\''; } break; default: @@ -1537,9 +1635,9 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) { - res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); - res.replace(QLatin1Char('.'), QLatin1String("`.`")); + if (!identifier.isEmpty() && !identifier.startsWith(u'`') && !identifier.endsWith(u'`')) { + res.replace(u'.', "`.`"_L1); + res = u'`' + res + u'`'; } return res; } @@ -1548,8 +1646,10 @@ bool QMYSQLDriver::isIdentifierEscaped(const QString &identifier, IdentifierType { Q_UNUSED(type); return identifier.size() > 2 - && identifier.startsWith(QLatin1Char('`')) //left delimited - && identifier.endsWith(QLatin1Char('`')); //right delimited + && identifier.startsWith(u'`') //left delimited + && identifier.endsWith(u'`'); //right delimited } QT_END_NAMESPACE + +#include "moc_qsql_mysql_p.cpp" diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h index 9ccc8f4e4f..ae3334d35e 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h +++ b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h @@ -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 #ifndef QSQL_MYSQL_H #define QSQL_MYSQL_H diff --git a/src/plugins/sqldrivers/oci/CMakeLists.txt b/src/plugins/sqldrivers/oci/CMakeLists.txt index 15ab7f7f87..66c4219905 100644 --- a/src/plugins/sqldrivers/oci/CMakeLists.txt +++ b/src/plugins/sqldrivers/oci/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from oci.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QOCIDriverPlugin Plugin: @@ -20,9 +21,6 @@ qt_internal_add_plugin(QOCIDriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:oci.pro:<TRUE>: -# OTHER_FILES = "oci.json" - ## Scopes: ##################################################################### diff --git a/src/plugins/sqldrivers/oci/main.cpp b/src/plugins/sqldrivers/oci/main.cpp index db273bfb04..6cc0062671 100644 --- a/src/plugins/sqldrivers/oci/main.cpp +++ b/src/plugins/sqldrivers/oci/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QOCIDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -51,7 +17,7 @@ class QOCIDriverPlugin : public QSqlDriverPlugin public: QOCIDriverPlugin(); - QSqlDriver* create(const QString &); + QSqlDriver* create(const QString &) override; }; QOCIDriverPlugin::QOCIDriverPlugin() @@ -61,7 +27,7 @@ QOCIDriverPlugin::QOCIDriverPlugin() QSqlDriver* QOCIDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QOCI")) { + if (name == "QOCI"_L1) { QOCIDriver* driver = new QOCIDriver(); return driver; } diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp index 638b01b022..68e303490d 100644 --- a/src/plugins/sqldrivers/oci/qsql_oci.cpp +++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 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_oci_p.h" @@ -43,6 +7,7 @@ #include <qdatetime.h> #include <qdebug.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qmetatype.h> #if QT_CONFIG(regularexpression) #include <qregularexpression.h> @@ -55,7 +20,9 @@ #include <QtSql/private/qsqlcachedresult_p.h> #include <QtSql/private/qsqldriver_p.h> #include <qstringlist.h> +#if QT_CONFIG(timezone) #include <qtimezone.h> +#endif #include <qvariant.h> #include <qvarlengtharray.h> @@ -64,14 +31,7 @@ #define _int64 __int64 #endif - #include <oci.h> -#ifdef max -#undef max -#endif -#ifdef min -#undef min -#endif #include <stdlib.h> @@ -92,6 +52,10 @@ Q_DECLARE_METATYPE(OCIStmt*) QT_BEGIN_NAMESPACE +static Q_LOGGING_CATEGORY(lcOci, "qt.sql.oci") + +using namespace Qt::StringLiterals; + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN enum { QOCIEncoding = 2002 }; // AL16UTF16LE #else @@ -172,8 +136,8 @@ QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt) if (dt.isValid()) { const QDate date = dt.date(); const QTime time = dt.time(); - // Zone in +hh:mm format (stripping UTC prefix from OffsetName) - QString timeZone = dt.timeZone().displayName(dt, QTimeZone::OffsetName).mid(3); + // Zone in +hh:mm format + const QString timeZone = dt.toString("ttt"_L1); const OraText *tz = reinterpret_cast<const OraText *>(timeZone.utf16()); OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec() * 1000000, @@ -202,7 +166,7 @@ QDateTime QOCIDateTime::fromOCIDateTime(OCIEnv *env, OCIError *err, OCIDateTime secondsOffset = -secondsOffset; // OCIDateTimeGetTime gives "fractions of second" as nanoseconds return QDateTime(QDate(year, month, day), QTime(hour, minute, second, nsec / 1000000), - Qt::OffsetFromUTC, secondsOffset); + QTimeZone::fromSecondsAheadOfUtc(secondsOffset)); } struct TempStorage { @@ -228,6 +192,7 @@ public: OCISession *authp = nullptr; OCITrans *trans = nullptr; OCIError *err = nullptr; + ub4 authMode = OCI_DEFAULT; bool transaction = false; int serverVersion = -1; int prefetchRows = -1; @@ -311,7 +276,7 @@ public: 0); #ifdef QOCI_DEBUG if (r != 0) - qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM."); + qCWarning(lcOci, "QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM."); #endif #endif @@ -458,6 +423,7 @@ int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, in break; } } // fall through for OUT values + Q_FALLTHROUGH(); default: { if (val.typeId() >= QMetaType::User) { if (val.canConvert<QOCIRowIdPointer>() && !isOutValue(pos)) { @@ -470,7 +436,7 @@ int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, in -1, SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT); } else { - qWarning("Unknown bind variable"); + qCWarning(lcOci, "Unknown bind variable"); r = OCI_ERROR; } } else { @@ -515,7 +481,7 @@ int QOCIResultPrivate::bindValues(QVariantList &values, IndicatorArray &indicato OCIBind * hbnd = nullptr; // Oracle handles these automatically sb2 *indPtr = &indicators[i]; - *indPtr = val.isNull() ? -1 : 0; + *indPtr = QSqlResultPrivate::isVariantNull(val) ? -1 : 0; bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage); } @@ -586,7 +552,7 @@ void QOCIDriverPrivate::allocErrorHandle() OCI_HTYPE_ERROR, 0, nullptr); if (r != OCI_SUCCESS) - qWarning("QOCIDriver: unable to allocate error handle"); + qCWarning(lcOci, "QOCIDriver: unable to allocate error handle"); } struct OraFieldInfo @@ -622,12 +588,7 @@ QString qOraWarn(OCIError *err, int *errorCode) void qOraWarning(const char* msg, OCIError *err) { -#ifdef QOCI_DEBUG - qWarning("%s %s", msg, qPrintable(qOraWarn(err))); -#else - Q_UNUSED(msg); - Q_UNUSED(err); -#endif + qCWarning(lcOci, "%s %ls", msg, qUtf16Printable(qOraWarn(err))); } static int qOraErrorNumber(OCIError *err) @@ -654,15 +615,15 @@ QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIErr QMetaType qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) { int type = QMetaType::UnknownType; - if (ocitype == QLatin1String("VARCHAR2") || ocitype == QLatin1String("VARCHAR") - || ocitype.startsWith(QLatin1String("INTERVAL")) - || ocitype == QLatin1String("CHAR") || ocitype == QLatin1String("NVARCHAR2") - || ocitype == QLatin1String("NCHAR")) + if (ocitype == "VARCHAR2"_L1 || ocitype == "VARCHAR"_L1 + || ocitype.startsWith("INTERVAL"_L1) + || ocitype == "CHAR"_L1 || ocitype == "NVARCHAR2"_L1 + || ocitype == "NCHAR"_L1) type = QMetaType::QString; - else if (ocitype == QLatin1String("NUMBER") - || ocitype == QLatin1String("FLOAT") - || ocitype == QLatin1String("BINARY_FLOAT") - || ocitype == QLatin1String("BINARY_DOUBLE")) { + else if (ocitype == "NUMBER"_L1 + || ocitype == "FLOAT"_L1 + || ocitype == "BINARY_FLOAT"_L1 + || ocitype == "BINARY_DOUBLE"_L1) { switch(precisionPolicy) { case QSql::LowPrecisionInt32: type = QMetaType::Int; @@ -679,19 +640,18 @@ QMetaType qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy break; } } - else if (ocitype == QLatin1String("LONG") || ocitype == QLatin1String("NCLOB") - || ocitype == QLatin1String("CLOB")) + else if (ocitype == "LONG"_L1 || ocitype == "NCLOB"_L1 || ocitype == "CLOB"_L1) type = QMetaType::QByteArray; - else if (ocitype == QLatin1String("RAW") || ocitype == QLatin1String("LONG RAW") - || ocitype == QLatin1String("ROWID") || ocitype == QLatin1String("BLOB") - || ocitype == QLatin1String("CFILE") || ocitype == QLatin1String("BFILE")) + else if (ocitype == "RAW"_L1 || ocitype == "LONG RAW"_L1 + || ocitype == "ROWID"_L1 || ocitype == "BLOB"_L1 + || ocitype == "CFILE"_L1 || ocitype == "BFILE"_L1) type = QMetaType::QByteArray; - else if (ocitype == QLatin1String("DATE") || ocitype.startsWith(QLatin1String("TIME"))) + else if (ocitype == "DATE"_L1 || ocitype.startsWith("TIME"_L1)) type = QMetaType::QDateTime; - else if (ocitype == QLatin1String("UNDEFINED")) + else if (ocitype == "UNDEFINED"_L1) type = QMetaType::UnknownType; if (type == QMetaType::UnknownType) - qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData()); + qCWarning(lcOci, "qDecodeOCIType: unknown type: %ls", qUtf16Printable(ocitype)); return QMetaType(type); } @@ -759,7 +719,7 @@ QMetaType qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPo type = QMetaType::QDateTime; break; default: - qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype); + qCWarning(lcOci, "qDecodeOCIType: unknown OCI datatype: %d", ocitype); break; } return QMetaType(type); @@ -776,7 +736,6 @@ static QSqlField qFromOraInf(const OraFieldInfo &ofi) f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision)); f.setPrecision(ofi.oraScale); - f.setSqlType(int(ofi.oraType)); return f; } @@ -875,7 +834,7 @@ QOCICols::OraFieldInf::~OraFieldInf() if (lob) { int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB); if (r != 0) - qWarning("QOCICols: Cannot free LOB descriptor"); + qCWarning(lcOci, "QOCICols: Cannot free LOB descriptor"); } if (dataPtr) { switch (typ.id()) { @@ -884,7 +843,7 @@ QOCICols::OraFieldInf::~OraFieldInf() case QMetaType::QDateTime: { int r = OCIDescriptorFree(dataPtr, OCI_DTYPE_TIMESTAMP_TZ); if (r != OCI_SUCCESS) - qWarning("QOCICols: Cannot free OCIDateTime descriptor"); + qCWarning(lcOci, "QOCICols: Cannot free OCIDateTime descriptor"); break; } default: @@ -939,7 +898,7 @@ QOCICols::QOCICols(int size, QOCIResultPrivate* dp) case QMetaType::QDateTime: r = OCIDescriptorAlloc(d->env, (void **)&fieldInf[idx].dataPtr, OCI_DTYPE_TIMESTAMP_TZ, 0, 0); if (r != OCI_SUCCESS) { - qWarning("QOCICols: Unable to allocate the OCIDateTime descriptor"); + qCWarning(lcOci, "QOCICols: Unable to allocate the OCIDateTime descriptor"); break; } r = OCIDefineByPos(d->sql, @@ -1110,7 +1069,7 @@ OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env) 0, 0); if (r != 0) { - qWarning("QOCICols: Cannot create LOB locator"); + qCWarning(lcOci, "QOCICols: Cannot create LOB locator"); lob = 0; } return &lob; @@ -1348,7 +1307,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a return false; #ifdef QOCI_DEBUG - qDebug() << "columnCount:" << columnCount << boundValues; + qCDebug(lcOci) << "columnCount:" << columnCount << boundValues; #endif int i; @@ -1372,7 +1331,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a // not a list - create a deep-copy of the single value QOCIBatchColumn &singleCol = columns[i]; singleCol.indicators = new sb2[1]; - *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0; + *singleCol.indicators = QSqlResultPrivate::isVariantNull(boundValues.at(i)) ? -1 : 0; r = d->bindValue(d->sql, &singleCol.bindh, d->err, i, boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage); @@ -1467,9 +1426,9 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a // we may now populate column with data for (uint row = 0; row < col.recordCount; ++row) { - const QVariant &val = boundValues.at(i).toList().at(row); + const QVariant val = boundValues.at(i).toList().at(row); - if (val.isNull() && !d->isOutValue(i)) { + if (QSqlResultPrivate::isVariantNull(val) && !d->isOutValue(i)) { columns[i].indicators[row] = -1; columns[i].lengths[row] = 0; } else { @@ -1482,6 +1441,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a columns[i].lengths[row] = columns[i].maxLen; QOCIDateTime *date = new QOCIDateTime(d->env, d->err, val.toDateTime()); *reinterpret_cast<OCIDateTime**>(dataPtr) = date->dateTime; + tmpStorage.dateTimes.append(date); break; } case QMetaType::Int: @@ -1544,13 +1504,13 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a QOCIBatchColumn &bindColumn = columns[i]; #ifdef QOCI_DEBUG - qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)", + qCDebug(lcOci, "OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)", d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data, bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths, arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0); for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) { - qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii], + qCDebug(lcOci, " record %d: indicator %d, length %d", ii, bindColumn.indicators[ii], bindColumn.lengths[ii]); } #endif @@ -1570,7 +1530,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a OCI_DEFAULT); #ifdef QOCI_DEBUG - qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh); + qCDebug(lcOci, "After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh); #endif if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { @@ -1597,7 +1557,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a } } - //finaly we can execute + //finally we can execute r = OCIStmtExecute(d->svc, d->sql, d->err, arrayBind ? 1 : columns[0].recordCount, 0, NULL, NULL, @@ -1830,7 +1790,7 @@ void QOCICols::getValues(QVariantList &v, int index) v[index + i] = QVariant(QMetaType(QMetaType::QByteArray)); break; default: - qWarning("QOCICols::value: unknown data type"); + qCWarning(lcOci, "QOCICols::value: unknown data type"); break; } } @@ -1851,7 +1811,7 @@ QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv) OCI_HTYPE_ERROR, 0, nullptr); if (r != OCI_SUCCESS) - qWarning("QOCIResult: unable to alloc error handle"); + qCWarning(lcOci, "QOCIResult: unable to alloc error handle"); } QOCIResultPrivate::~QOCIResultPrivate() @@ -1859,10 +1819,10 @@ QOCIResultPrivate::~QOCIResultPrivate() delete cols; if (sql && OCIHandleFree(sql, OCI_HTYPE_STMT) != OCI_SUCCESS) - qWarning("~QOCIResult: unable to free statement handle"); + qCWarning(lcOci, "~QOCIResult: unable to free statement handle"); if (OCIHandleFree(err, OCI_HTYPE_ERROR) != OCI_SUCCESS) - qWarning("~QOCIResult: unable to free error report handle"); + qCWarning(lcOci, "~QOCIResult: unable to free error report handle"); } @@ -1919,7 +1879,7 @@ bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) break; case OCI_ERROR: if (qOraErrorNumber(d->err) == 1406) { - qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData()); + qCWarning(lcOci, "QOCI Warning: data truncated for %ls", qUtf16Printable(lastQuery())); r = OCI_SUCCESS; /* ignore it */ break; } @@ -2033,7 +1993,7 @@ bool QOCIResult::exec() setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to get statement type"), QSqlError::StatementError, d->err)); #ifdef QOCI_DEBUG - qDebug() << "lastQuery()" << lastQuery(); + qCDebug(lcOci) << "lastQuery()" << lastQuery(); #endif return false; } @@ -2048,7 +2008,7 @@ bool QOCIResult::exec() setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"), QSqlError::StatementError, d->err)); #ifdef QOCI_DEBUG - qDebug() << "lastQuery()" << lastQuery(); + qCDebug(lcOci) << "lastQuery()" << lastQuery(); #endif return false; } @@ -2067,7 +2027,7 @@ bool QOCIResult::exec() setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to execute statement"), QSqlError::StatementError, d->err)); #ifdef QOCI_DEBUG - qDebug() << "lastQuery()" << lastQuery(); + qCDebug(lcOci) << "lastQuery()" << lastQuery(); #endif return false; } @@ -2159,7 +2119,7 @@ QOCIDriver::QOCIDriver(QObject* parent) 0, NULL); if (r != 0) { - qWarning("QOCIDriver: unable to create environment"); + qCWarning(lcOci, "QOCIDriver: unable to create environment"); setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"), QSqlError::ConnectionError, d->err)); return; @@ -2190,10 +2150,10 @@ QOCIDriver::~QOCIDriver() close(); int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR); if (r != OCI_SUCCESS) - qWarning("Unable to free Error handle: %d", r); + qCWarning(lcOci, "Unable to free Error handle: %d", r); r = OCIHandleFree(d->env, OCI_HTYPE_ENV); if (r != OCI_SUCCESS) - qWarning("Unable to free Environment handle: %d", r); + qCWarning(lcOci, "Unable to free Environment handle: %d", r); } bool QOCIDriver::hasFeature(DriverFeature f) const @@ -2224,29 +2184,37 @@ bool QOCIDriver::hasFeature(DriverFeature f) const static void qParseOpts(const QString &options, QOCIDriverPrivate *d) { - const QStringList opts(options.split(QLatin1Char(';'), Qt::SkipEmptyParts)); - for (int i = 0; i < opts.count(); ++i) { - const QString tmp(opts.at(i)); - int idx; - if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { - qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'", - tmp.toLocal8Bit().constData()); + const QVector<QStringView> opts(QStringView(options).split(u';', Qt::SkipEmptyParts)); + for (const auto tmp : opts) { + qsizetype idx; + if ((idx = tmp.indexOf(u'=')) == -1) { + qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'", + qUtf16Printable(tmp.toString())); continue; } - const QString opt = tmp.left(idx); - const QString val = tmp.mid(idx + 1).simplified(); + const QStringView opt = tmp.left(idx); + const QStringView val = tmp.mid(idx + 1).trimmed(); bool ok; - if (opt == QLatin1String("OCI_ATTR_PREFETCH_ROWS")) { + if (opt == "OCI_ATTR_PREFETCH_ROWS"_L1) { d->prefetchRows = val.toInt(&ok); if (!ok) d->prefetchRows = -1; - } else if (opt == QLatin1String("OCI_ATTR_PREFETCH_MEMORY")) { + } else if (opt == "OCI_ATTR_PREFETCH_MEMORY"_L1) { d->prefetchMem = val.toInt(&ok); if (!ok) d->prefetchMem = -1; + } else if (opt == "OCI_AUTH_MODE"_L1) { + if (val == "OCI_SYSDBA"_L1) { + d->authMode = OCI_SYSDBA; + } else if (val == "OCI_SYSOPER"_L1) { + d->authMode = OCI_SYSOPER; + } else if (val != "OCI_DEFAULT"_L1) { + qCWarning(lcOci, "QOCIDriver::parseArgs: Unsupported value for OCI_AUTH_MODE: '%ls'", + qUtf16Printable(val.toString())); + } } else { - qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'", - opt.toLocal8Bit().constData()); + qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'", + qUtf16Printable(opt.toString())); } } } @@ -2310,9 +2278,9 @@ bool QOCIDriver::open(const QString & db, if (r == OCI_SUCCESS) { if (user.isEmpty() && password.isEmpty()) - r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT); + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, d->authMode); else - r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT); + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, d->authMode); } if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err); @@ -2343,12 +2311,12 @@ bool QOCIDriver::open(const QString & db, sizeof(vertxt), OCI_HTYPE_SVCCTX); if (r != 0) { - qWarning("QOCIDriver::open: could not get Oracle server version."); + qCWarning(lcOci, "QOCIDriver::open: could not get Oracle server version."); } else { QString versionStr; versionStr = QString(reinterpret_cast<const QChar *>(vertxt)); #if QT_CONFIG(regularexpression) - auto match = QRegularExpression(QLatin1String("([0-9]+)\\.[0-9\\.]+[0-9]")).match(versionStr); + auto match = QRegularExpression("([0-9]+)\\.[0-9\\.]+[0-9]"_L1).match(versionStr); if (match.hasMatch()) d->serverVersion = match.captured(1).toInt(); #endif @@ -2392,7 +2360,7 @@ bool QOCIDriver::beginTransaction() { Q_D(QOCIDriver); if (!isOpen()) { - qWarning("QOCIDriver::beginTransaction: Database not open"); + qCWarning(lcOci, "QOCIDriver::beginTransaction: Database not open"); return false; } int r = OCITransStart(d->svc, @@ -2413,7 +2381,7 @@ bool QOCIDriver::commitTransaction() { Q_D(QOCIDriver); if (!isOpen()) { - qWarning("QOCIDriver::commitTransaction: Database not open"); + qCWarning(lcOci, "QOCIDriver::commitTransaction: Database not open"); return false; } int r = OCITransCommit(d->svc, @@ -2433,7 +2401,7 @@ bool QOCIDriver::rollbackTransaction() { Q_D(QOCIDriver); if (!isOpen()) { - qWarning("QOCIDriver::rollbackTransaction: Database not open"); + qCWarning(lcOci, "QOCIDriver::rollbackTransaction: Database not open"); return false; } int r = OCITransRollback(d->svc, @@ -2466,18 +2434,18 @@ static QString make_where_clause(const QString &user, Expression e) "WMSYS", }; static const char joinC[][4] = { "or" , "and" }; - static constexpr QLatin1Char bang[] = { QLatin1Char(' '), QLatin1Char('!') }; + static constexpr char16_t bang[] = { u' ', u'!' }; - const QLatin1String join(joinC[e]); + const QLatin1StringView join(joinC[e]); QString result; result.reserve(sizeof sysUsers / sizeof *sysUsers * // max-sizeof(owner != <sysuser> and ) (9 + sizeof *sysUsers + 5)); for (const auto &sysUser : sysUsers) { - const QLatin1String l1(sysUser); + const QLatin1StringView l1(sysUser); if (l1 != user) - result += QLatin1String("owner ") + bang[e] + QLatin1String("= '") + l1 + QLatin1String("' ") + join + QLatin1Char(' '); + result += "owner "_L1 + bang[e] + "= '"_L1 + l1 + "' "_L1 + join + u' '; } result.chop(join.size() + 2); // remove final " <join> " @@ -2502,58 +2470,58 @@ QStringList QOCIDriver::tables(QSql::TableType type) const QSqlQuery t(createResult()); t.setForwardOnly(true); if (type & QSql::Tables) { - const QLatin1String tableQuery("select owner, table_name from all_tables where "); + const auto tableQuery = "select owner, table_name from all_tables where "_L1; const QString where = make_where_clause(user, AndExpression); t.exec(tableQuery + where); while (t.next()) { if (t.value(0).toString().toUpper() != user.toUpper()) - tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + tl.append(t.value(0).toString() + u'.' + t.value(1).toString()); else tl.append(t.value(1).toString()); } // list all table synonyms as well - const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); + const auto synonymQuery = "select owner, synonym_name from all_synonyms where "_L1; t.exec(synonymQuery + where); while (t.next()) { if (t.value(0).toString() != d->user) - tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + tl.append(t.value(0).toString() + u'.' + t.value(1).toString()); else tl.append(t.value(1).toString()); } } if (type & QSql::Views) { - const QLatin1String query("select owner, view_name from all_views where "); + const auto query = "select owner, view_name from all_views where "_L1; const QString where = make_where_clause(user, AndExpression); t.exec(query + where); while (t.next()) { if (t.value(0).toString().toUpper() != d->user.toUpper()) - tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + tl.append(t.value(0).toString() + u'.' + t.value(1).toString()); else tl.append(t.value(1).toString()); } } if (type & QSql::SystemTables) { - t.exec(QLatin1String("select table_name from dictionary")); + t.exec("select table_name from dictionary"_L1); while (t.next()) { tl.append(t.value(0).toString()); } - const QLatin1String tableQuery("select owner, table_name from all_tables where "); + const auto tableQuery = "select owner, table_name from all_tables where "_L1; const QString where = make_where_clause(user, OrExpression); t.exec(tableQuery + where); while (t.next()) { if (t.value(0).toString().toUpper() != user.toUpper()) - tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + tl.append(t.value(0).toString() + u'.' + t.value(1).toString()); else tl.append(t.value(1).toString()); } // list all table synonyms as well - const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); + const auto synonymQuery = "select owner, synonym_name from all_synonyms where "_L1; t.exec(synonymQuery + where); while (t.next()) { if (t.value(0).toString() != d->user) - tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); + tl.append(t.value(0).toString() + u'.' + t.value(1).toString()); else tl.append(t.value(1).toString()); } @@ -2564,7 +2532,7 @@ QStringList QOCIDriver::tables(QSql::TableType type) const void qSplitTableAndOwner(const QString & tname, QString * tbl, QString * owner) { - int i = tname.indexOf(QLatin1Char('.')); // prefixed with owner? + qsizetype i = tname.indexOf(u'.'); // prefixed with owner? if (i != -1) { *tbl = tname.right(tname.length() - i - 1); *owner = tname.left(i); @@ -2583,14 +2551,13 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const QSqlQuery t(createResult()); // using two separate queries for this is A LOT faster than using // eg. a sub-query on the sys.synonyms table - QString stmt(QLatin1String("select column_name, data_type, data_length, " + QString stmt("select column_name, data_type, data_length, " "data_precision, data_scale, nullable, data_default%1" - "from all_tab_columns a " - "where a.table_name=%2")); + "from all_tab_columns a "_L1); if (d->serverVersion >= 9) - stmt = stmt.arg(QLatin1String(", char_length ")); + stmt = stmt.arg(", char_length "_L1); else - stmt = stmt.arg(QLatin1String(" ")); + stmt = stmt.arg(" "_L1); bool buildRecordInfo = false; QString table, owner, tmpStmt; qSplitTableAndOwner(tablename, &table, &owner); @@ -2600,7 +2567,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const else table = table.toUpper(); - tmpStmt = stmt.arg(QLatin1Char('\'') + table + QLatin1Char('\'')); + tmpStmt = stmt + "where a.table_name='"_L1 + table + u'\''; if (owner.isEmpty()) { owner = d->user; } @@ -2610,15 +2577,12 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const else owner = owner.toUpper(); - tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); + tmpStmt += " and a.owner='"_L1 + owner + u'\''; t.setForwardOnly(true); t.exec(tmpStmt); if (!t.next()) { // try and see if the tablename is a synonym - stmt = stmt + QLatin1String(" join all_synonyms b " - "on a.owner=b.table_owner and a.table_name=b.table_name " - "where b.owner='") + owner + - QLatin1String("' and b.synonym_name='") + table + - QLatin1Char('\''); + stmt = stmt + " join all_synonyms b on a.owner=b.table_owner and a.table_name=b.table_name " + "where b.owner='"_L1 + owner + "' and b.synonym_name='"_L1 + table + u'\''; t.setForwardOnly(true); t.exec(stmt); if (t.next()) @@ -2626,13 +2590,13 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const } else { buildRecordInfo = true; } - QStringList keywords = QStringList() << QLatin1String("NUMBER") << QLatin1String("FLOAT") << QLatin1String("BINARY_FLOAT") - << QLatin1String("BINARY_DOUBLE"); + QStringList keywords = QStringList() << "NUMBER"_L1 << "FLOAT"_L1 << "BINARY_FLOAT"_L1 + << "BINARY_DOUBLE"_L1; if (buildRecordInfo) { do { QMetaType ty = qDecodeOCIType(t.value(1).toString(), t.numericalPrecisionPolicy()); QSqlField f(t.value(0).toString(), ty); - f.setRequired(t.value(5).toString() == QLatin1String("N")); + f.setRequired(t.value(5).toString() == "N"_L1); f.setPrecision(t.value(4).toInt()); if (d->serverVersion >= 9 && (ty.id() == QMetaType::QString) && !t.isNull(3) && !keywords.contains(t.value(1).toString())) { // Oracle9: data_length == size in bytes, char_length == amount of characters @@ -2654,11 +2618,11 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const if (!isOpen()) return idx; QSqlQuery t(createResult()); - QString stmt(QLatin1String("select b.column_name, b.index_name, a.table_name, a.owner " + QString stmt("select b.column_name, b.index_name, a.table_name, a.owner " "from all_constraints a, all_ind_columns b " "where a.constraint_type='P' " "and b.index_name = a.index_name " - "and b.index_owner = a.owner")); + "and b.index_owner = a.owner"_L1); bool buildIndex = false; QString table, owner, tmpStmt; @@ -2669,7 +2633,7 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const else table = table.toUpper(); - tmpStmt = stmt + QLatin1String(" and a.table_name='") + table + QLatin1Char('\''); + tmpStmt = stmt + " and a.table_name='"_L1 + table + u'\''; if (owner.isEmpty()) { owner = d->user; } @@ -2679,13 +2643,13 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const else owner = owner.toUpper(); - tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); + tmpStmt += " and a.owner='"_L1 + owner + u'\''; t.setForwardOnly(true); t.exec(tmpStmt); if (!t.next()) { - stmt += QLatin1String(" and a.table_name=(select tname from sys.synonyms " - "where sname='") + table + QLatin1String("' and creator=a.owner)"); + stmt += " and a.table_name=(select tname from sys.synonyms where sname='"_L1 + + table + "' and creator=a.owner)"_L1; t.setForwardOnly(true); t.exec(stmt); if (t.next()) { @@ -2700,10 +2664,10 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const tt.setForwardOnly(true); idx.setName(t.value(1).toString()); do { - tt.exec(QLatin1String("select data_type from all_tab_columns where table_name='") + - t.value(2).toString() + QLatin1String("' and column_name='") + - t.value(0).toString() + QLatin1String("' and owner='") + - owner + QLatin1Char('\'')); + tt.exec("select data_type from all_tab_columns where table_name='"_L1 + + t.value(2).toString() + "' and column_name='"_L1 + + t.value(0).toString() + "' and owner='"_L1 + + owner + u'\''); if (!tt.next()) { return QSqlIndex(); } @@ -2717,21 +2681,21 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const { - switch (field.typeID()) { + switch (field.metaType().id()) { case QMetaType::QDateTime: { QDateTime datetime = field.value().toDateTime(); QString datestring; if (datetime.isValid()) { - datestring = QLatin1String("TO_DATE('") + QString::number(datetime.date().year()) - + QLatin1Char('-') - + QString::number(datetime.date().month()) + QLatin1Char('-') - + QString::number(datetime.date().day()) + QLatin1Char(' ') - + QString::number(datetime.time().hour()) + QLatin1Char(':') - + QString::number(datetime.time().minute()) + QLatin1Char(':') + datestring = "TO_DATE('"_L1 + QString::number(datetime.date().year()) + + u'-' + + QString::number(datetime.date().month()) + u'-' + + QString::number(datetime.date().day()) + u' ' + + QString::number(datetime.time().hour()) + u':' + + QString::number(datetime.time().minute()) + u':' + QString::number(datetime.time().second()) - + QLatin1String("','YYYY-MM-DD HH24:MI:SS')"); + + "','YYYY-MM-DD HH24:MI:SS')"_L1; } else { - datestring = QLatin1String("NULL"); + datestring = "NULL"_L1; } return datestring; } @@ -2739,13 +2703,13 @@ QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const QDateTime datetime = field.value().toDateTime(); QString datestring; if (datetime.isValid()) { - datestring = QLatin1String("TO_DATE('") - + QString::number(datetime.time().hour()) + QLatin1Char(':') - + QString::number(datetime.time().minute()) + QLatin1Char(':') + datestring = "TO_DATE('"_L1 + + QString::number(datetime.time().hour()) + u':' + + QString::number(datetime.time().minute()) + u':' + QString::number(datetime.time().second()) - + QLatin1String("','HH24:MI:SS')"); + + "','HH24:MI:SS')"_L1; } else { - datestring = QLatin1String("NULL"); + datestring = "NULL"_L1; } return datestring; } @@ -2753,12 +2717,12 @@ QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const QDate date = field.value().toDate(); QString datestring; if (date.isValid()) { - datestring = QLatin1String("TO_DATE('") + QString::number(date.year()) + - QLatin1Char('-') + - QString::number(date.month()) + QLatin1Char('-') + - QString::number(date.day()) + QLatin1String("','YYYY-MM-DD')"); + datestring = "TO_DATE('"_L1 + QString::number(date.year()) + + u'-' + + QString::number(date.month()) + u'-' + + QString::number(date.day()) + "','YYYY-MM-DD')"_L1; } else { - datestring = QLatin1String("NULL"); + datestring = "NULL"_L1; } return datestring; } @@ -2778,11 +2742,20 @@ QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType t { QString res = identifier; if (!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + res.replace(u'"', "\"\""_L1); + res.replace(u'.', "\".\""_L1); + res = u'"' + res + u'"'; } return res; } +int QOCIDriver::maximumIdentifierLength(IdentifierType type) const +{ + Q_D(const QOCIDriver); + Q_UNUSED(type); + return d->serverVersion > 12 ? 128 : 30; +} + QT_END_NAMESPACE + +#include "moc_qsql_oci_p.cpp" diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h index a3366abb77..91fef9de7b 100644 --- a/src/plugins/sqldrivers/oci/qsql_oci_p.h +++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h @@ -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 #ifndef QSQL_OCI_H #define QSQL_OCI_H @@ -78,7 +42,7 @@ public: explicit QOCIDriver(QObject *parent = nullptr); QOCIDriver(OCIEnv *env, OCISvcCtx *ctx, QObject *parent = nullptr); ~QOCIDriver(); - bool hasFeature(DriverFeature f) const; + bool hasFeature(DriverFeature f) const override; bool open(const QString &db, const QString &user, const QString &password, @@ -94,6 +58,7 @@ public: bool trimStrings) const override; QVariant handle() const override; QString escapeIdentifier(const QString &identifier, IdentifierType) const override; + int maximumIdentifierLength(IdentifierType type) const override; protected: bool beginTransaction() override; diff --git a/src/plugins/sqldrivers/odbc/CMakeLists.txt b/src/plugins/sqldrivers/odbc/CMakeLists.txt index 1bbae1b3ec..7812aced2c 100644 --- a/src/plugins/sqldrivers/odbc/CMakeLists.txt +++ b/src/plugins/sqldrivers/odbc/CMakeLists.txt @@ -1,10 +1,11 @@ -# Generated from odbc.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QODBCDriverPlugin Plugin: ##################################################################### -qt_find_package(ODBC) # special case +qt_find_package(ODBC) qt_internal_add_plugin(QODBCDriverPlugin OUTPUT_NAME qsqlodbc @@ -15,6 +16,7 @@ qt_internal_add_plugin(QODBCDriverPlugin DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES ODBC::ODBC Qt::Core @@ -22,9 +24,6 @@ qt_internal_add_plugin(QODBCDriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:odbc.pro:<TRUE>: -# OTHER_FILES = "odbc.json" - ## Scopes: ##################################################################### @@ -32,5 +31,3 @@ qt_internal_extend_target(QODBCDriverPlugin CONDITION UNIX DEFINES UNICODE ) - -qt_internal_force_macos_intel_arch(QODBCDriverPlugin) diff --git a/src/plugins/sqldrivers/odbc/main.cpp b/src/plugins/sqldrivers/odbc/main.cpp index 92478ecbd1..0310be0c64 100644 --- a/src/plugins/sqldrivers/odbc/main.cpp +++ b/src/plugins/sqldrivers/odbc/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QODBCDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,7 +27,7 @@ QODBCDriverPlugin::QODBCDriverPlugin() QSqlDriver* QODBCDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QODBC")) { + if (name == "QODBC"_L1) { QODBCDriver* driver = new QODBCDriver(); return driver; } diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 1b3a55d096..976911d458 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 +static Q_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"); +} + +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(const QString &input) +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 = nullptr) +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 = nullptr) +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 = nullptr) +static QList<DiagRecord> qODBCWarn(const QODBCResultPrivate *odbc) { - return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode); + return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc()); } -static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = nullptr) +static QList<DiagRecord> qODBCWarn(const QODBCDriverPrivate *odbc) { - return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode); + return qODBCWarn(nullptr, odbc->hEnv, odbc->hDbc); } -static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc) +static DiagRecord combineRecords(const QList<DiagRecord> &records) { - qWarning() << message << "\tError:" << qODBCWarn(odbc); + 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 QSqlError errorFromDiagRecords(const QString &err, + QSqlError::ErrorType type, + const QList<DiagRecord> &records) +{ + 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) @@ -376,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; @@ -404,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; @@ -528,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; @@ -555,7 +508,7 @@ 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(QMetaType::QByteArray)); @@ -584,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>()); @@ -604,7 +557,7 @@ 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) @@ -624,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>()); @@ -640,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) @@ -672,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; @@ -689,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, @@ -702,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, @@ -715,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) @@ -731,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; } @@ -758,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()); +} -bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) +SQLRETURN QODBCDriverPrivate::sqlFetchNext(SQLHANDLE hStmt) const +{ + if (hasSQLFetchScroll) + return SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); + return SQLFetch(hStmt); +} + +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; } /* @@ -945,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; } //////////////////////////////////////////////////////////////////////////// @@ -974,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); } } @@ -994,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; } } @@ -1002,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; } @@ -1019,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; @@ -1037,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); @@ -1106,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)); @@ -1203,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) @@ -1239,7 +1199,7 @@ 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>()); @@ -1252,7 +1212,7 @@ 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>()); @@ -1265,7 +1225,7 @@ 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 @@ -1295,7 +1255,7 @@ QVariant QODBCResult::data(int field) } 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; @@ -1308,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) @@ -1329,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; } @@ -1345,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; } } @@ -1353,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; } @@ -1370,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", @@ -1399,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; } @@ -1407,19 +1369,17 @@ 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.typeId()) { case QMetaType::QDate: { @@ -1622,36 +1582,36 @@ bool QODBCResult::exec() 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; @@ -1694,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; @@ -1710,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); @@ -1731,7 +1691,7 @@ bool QODBCResult::exec() if (!hasOutValues()) return true; - for (i = 0; i < values.count(); ++i) { + 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())); @@ -1762,10 +1722,11 @@ bool QODBCResult::exec() 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; } @@ -1797,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; @@ -1814,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(); @@ -1841,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", @@ -1856,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); @@ -1962,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(); @@ -1969,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; } @@ -1981,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; @@ -1996,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(); @@ -2042,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; } @@ -2068,21 +2040,21 @@ void QODBCDriver::cleanup() 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; } } @@ -2100,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; } @@ -2110,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; } @@ -2120,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); + 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); + r = SQLFetch(hStmt.handle()); 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")) { + 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; } } @@ -2206,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, @@ -2240,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; } } @@ -2251,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 @@ -2302,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); @@ -2322,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, @@ -2340,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, @@ -2375,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; } @@ -2452,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); - - if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) - catalog = stripDelimiters(catalog, QSqlDriver::TableName); - else - catalog = d->adjustCase(catalog); + d->splitTableQualifier(tablename, catalog, schema, table); - 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; } @@ -2554,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; } @@ -2628,33 +2526,33 @@ QString QODBCDriver::formatValue(const QSqlField &field, { QString r; if (field.isNull()) { - r = QLatin1String("NULL"); + 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"); + r = "NULL"_L1; } else if (field.metaType().id() == QMetaType::QByteArray) { - 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]); + 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); } @@ -2673,9 +2571,10 @@ QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) 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)); + const QString quoteStr(quote); + res.replace(quote, quoteStr + quoteStr); + res.replace(u'.', quoteStr + u'.' + quoteStr); + res = quote + res + quote; } return res; } diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc_p.h b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h index ccd0206f38..23703b5d03 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc_p.h +++ b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h @@ -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 #ifndef QSQL_ODBC_H #define QSQL_ODBC_H diff --git a/src/plugins/sqldrivers/psql/CMakeLists.txt b/src/plugins/sqldrivers/psql/CMakeLists.txt index 8ed84c9028..2f55ab4950 100644 --- a/src/plugins/sqldrivers/psql/CMakeLists.txt +++ b/src/plugins/sqldrivers/psql/CMakeLists.txt @@ -1,6 +1,7 @@ -# Generated from psql.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause -qt_find_package(PostgreSQL) # special case +qt_find_package(PostgreSQL) ##################################################################### ## QPSQLDriverPlugin Plugin: @@ -15,6 +16,7 @@ qt_internal_add_plugin(QPSQLDriverPlugin DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES PostgreSQL::PostgreSQL Qt::Core @@ -22,9 +24,6 @@ qt_internal_add_plugin(QPSQLDriverPlugin Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:psql.pro:<TRUE>: -# OTHER_FILES = "psql.json" - # PostgreSQL delivers header files that are not a part of PostgreSQL itself. When precompiled # headers are processed, MinGW uses 'pthread.h' from the PostgreSQL installation directory. # As result, we disable precompile headers for the plugin. @@ -35,5 +34,3 @@ if(MINGW) DISABLE_PRECOMPILE_HEADERS ON ) endif() - -qt_internal_force_macos_intel_arch(QPSQLDriverPlugin) diff --git a/src/plugins/sqldrivers/psql/main.cpp b/src/plugins/sqldrivers/psql/main.cpp index c31e6f64b7..e473fe1edc 100644 --- a/src/plugins/sqldrivers/psql/main.cpp +++ b/src/plugins/sqldrivers/psql/main.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> @@ -43,6 +7,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QPSQLDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -61,7 +27,7 @@ QPSQLDriverPlugin::QPSQLDriverPlugin() QSqlDriver* QPSQLDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QPSQL")) + if (name == "QPSQL"_L1) return new QPSQLDriver; return nullptr; } diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp index 4c3d6ca13f..7b9521fec6 100644 --- a/src/plugins/sqldrivers/psql/qsql_psql.cpp +++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp @@ -1,47 +1,12 @@ -/**************************************************************************** -** -** 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_psql_p.h" #include <qcoreapplication.h> #include <qvariant.h> #include <qdatetime.h> +#include <qloggingcategory.h> #include <qregularexpression.h> #include <qsqlerror.h> #include <qsqlfield.h> @@ -101,6 +66,10 @@ Q_DECLARE_METATYPE(PGresult*) QT_BEGIN_NAMESPACE +static Q_LOGGING_CATEGORY(lcPsql, "qt.sql.postgresql") + +using namespace Qt::StringLiterals; + inline void qPQfreemem(void *buffer) { PQfreemem(buffer); @@ -108,11 +77,11 @@ inline void qPQfreemem(void *buffer) /* Missing declaration of PGRES_SINGLE_TUPLE for PSQL below 9.2 */ #if !defined PG_VERSION_NUM || PG_VERSION_NUM-0 < 90200 -static const int PGRES_SINGLE_TUPLE = 9; +static constexpr int PGRES_SINGLE_TUPLE = 9; #endif typedef int StatementId; -static const StatementId InvalidStatementId = 0; +static constexpr StatementId InvalidStatementId = 0; class QPSQLResultPrivate; @@ -156,10 +125,9 @@ public: QSocketNotifier *sn = nullptr; QPSQLDriver::Protocol pro = QPSQLDriver::Version6; StatementId currentStmtId = InvalidStatementId; - int stmtCount = 0; + StatementId stmtCount = InvalidStatementId; mutable bool pendingNotifyCheck = false; bool hasBackslashEscape = false; - bool isUtf8 = false; void appendTables(QStringList &tl, QSqlQuery &t, QChar type); PGresult *exec(const char *stmt); @@ -175,6 +143,7 @@ public: bool setEncodingUtf8(); void setDatestyle(); void setByteaOutput(); + void setUtcTimeZone(); void detectBackslashEscape(); mutable QHash<int, QString> oidToTable; }; @@ -191,10 +160,10 @@ void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type) t.exec(query); while (t.next()) { QString schema = t.value(1).toString(); - if (schema.isEmpty() || schema == QLatin1String("public")) + if (schema.isEmpty() || schema == "public"_L1) tl.append(t.value(0).toString()); else - tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema)); + tl.append(t.value(0).toString().prepend(u'.').prepend(schema)); } } @@ -209,7 +178,7 @@ PGresult *QPSQLDriverPrivate::exec(const char *stmt) PGresult *QPSQLDriverPrivate::exec(const QString &stmt) { - return exec((isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData()); + return exec(stmt.toUtf8().constData()); } StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt) @@ -217,8 +186,7 @@ StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt) // Discard any prior query results that the application didn't eat. // This is required for PQsendQuery() discardResults(); - const int result = PQsendQuery(connection, - (isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData()); + const int result = PQsendQuery(connection, stmt.toUtf8().constData()); currentStmtId = result ? generateStatementId() : InvalidStatementId; return currentStmtId; } @@ -243,8 +211,8 @@ PGresult *QPSQLDriverPrivate::getResult(StatementId stmtId) const if (stmtId != currentStmtId) { // If you change the following warning, remember to update it // on sql-driver.html page too. - qWarning("QPSQLDriver::getResult: Query results lost - " - "probably discarded on executing another SQL query."); + qCWarning(lcPsql, "QPSQLDriver::getResult: Query results lost - " + "probably discarded on executing another SQL query."); return nullptr; } PGresult *result = PQgetResult(connection); @@ -268,7 +236,7 @@ void QPSQLDriverPrivate::discardResults() const StatementId QPSQLDriverPrivate::generateStatementId() { - int stmtId = ++stmtCount; + StatementId stmtId = ++stmtCount; if (stmtId <= 0) stmtId = stmtCount = 1; return stmtId; @@ -279,18 +247,18 @@ void QPSQLDriverPrivate::checkPendingNotifications() const Q_Q(const QPSQLDriver); if (seid.size() && !pendingNotifyCheck) { pendingNotifyCheck = true; - QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection); + QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), &QPSQLDriver::_q_handleNotification, Qt::QueuedConnection); } } -class QPSQLResultPrivate : public QSqlResultPrivate +class QPSQLResultPrivate final : public QSqlResultPrivate { Q_DECLARE_PUBLIC(QPSQLResult) public: Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver) using QSqlResultPrivate::QSqlResultPrivate; - QString fieldSerial(int i) const override { return QLatin1Char('$') + QString::number(i + 1); } + QString fieldSerial(qsizetype i) const override { return QString("$%1"_L1).arg(i + 1); } void deallocatePreparedStmt(); std::queue<PGresult*> nextResultSets; @@ -308,13 +276,13 @@ static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type, const QPSQLDriverPrivate *p, PGresult *result = nullptr) { const char *s = PQerrorMessage(p->connection); - QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s); + QString msg = QString::fromUtf8(s); QString errorCode; if (result) { errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE)); msg += QString::fromLatin1("(%1)").arg(errorCode); } - return QSqlError(QLatin1String("QPSQL: ") + err, msg, type, errorCode); + return QSqlError("QPSQL: "_L1 + err, msg, type, errorCode); } bool QPSQLResultPrivate::processResults() @@ -416,8 +384,10 @@ void QPSQLResultPrivate::deallocatePreparedStmt() const QString stmt = QStringLiteral("DEALLOCATE ") + preparedStmtId; PGresult *result = drv_d_func()->exec(stmt); - if (PQresultStatus(result) != PGRES_COMMAND_OK) - qWarning("Unable to free statement: %s", PQerrorMessage(drv_d_func()->connection)); + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection)); + qCWarning(lcPsql, "Unable to free statement: %ls.", qUtf16Printable(msg)); + } PQclear(result); } preparedStmtId.clear(); @@ -627,7 +597,7 @@ QVariant QPSQLResult::data(int i) { Q_D(const QPSQLResult); if (i >= PQnfields(d->result)) { - qWarning("QPSQLResult::data: column %d out of range", i); + qCWarning(lcPsql, "QPSQLResult::data: column %d out of range.", i); return QVariant(); } const int currentRow = isForwardOnly() ? 0 : at(); @@ -640,7 +610,7 @@ QVariant QPSQLResult::data(int i) case QMetaType::Bool: return QVariant((bool)(val[0] == 't')); case QMetaType::QString: - return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val); + return QString::fromUtf8(val); case QMetaType::LongLong: if (val[0] == '-') return QByteArray::fromRawData(val, qstrlen(val)).toLongLong(); @@ -675,23 +645,21 @@ QVariant QPSQLResult::data(int i) } return dbl; } - case QMetaType::QDate: #if QT_CONFIG(datestring) + case QMetaType::QDate: return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate)); -#else - return QVariant(QString::fromLatin1(val)); -#endif case QMetaType::QTime: -#if QT_CONFIG(datestring) return QVariant(QTime::fromString(QString::fromLatin1(val), Qt::ISODate)); + case QMetaType::QDateTime: { + QString tzString(QString::fromLatin1(val)); + if (!tzString.endsWith(u'Z')) + tzString.append(u'Z'); // make UTC + return QVariant(QDateTime::fromString(tzString, Qt::ISODate)); + } #else - return QVariant(QString::fromLatin1(val)); -#endif + case QMetaType::QDate: + case QMetaType::QTime: case QMetaType::QDateTime: -#if QT_CONFIG(datestring) - return QVariant(QDateTime::fromString(QString::fromLatin1(val), - Qt::ISODate).toLocalTime()); -#else return QVariant(QString::fromLatin1(val)); #endif case QMetaType::QByteArray: { @@ -702,7 +670,7 @@ QVariant QPSQLResult::data(int i) return QVariant(ba); } default: - qWarning("QPSQLResult::data: unknown data type"); + qCWarning(lcPsql, "QPSQLResult::data: unhandled data type %d.", type.id()); } return QVariant(); } @@ -781,10 +749,7 @@ QSqlRecord QPSQLResult::record() const int count = PQnfields(d->result); QSqlField f; for (int i = 0; i < count; ++i) { - if (d->drv_d_func()->isUtf8) - f.setName(QString::fromUtf8(PQfname(d->result, i))); - else - f.setName(QString::fromLocal8Bit(PQfname(d->result, i))); + f.setName(QString::fromUtf8(PQfname(d->result, i))); const int tableOid = PQftable(d->result, i); // WARNING: We cannot execute any other SQL queries on // the same db connection while forward-only mode is active @@ -835,7 +800,6 @@ QSqlRecord QPSQLResult::record() const f.setLength(len); f.setPrecision(precision); - f.setSqlType(ptype); info.append(f); } return info; @@ -856,12 +820,12 @@ static QString qCreateParamString(const QList<QVariant> &boundValues, const QSql QSqlField f; for (const QVariant &val : boundValues) { f.setMetaType(val.metaType()); - if (val.isNull()) + if (QSqlResultPrivate::isVariantNull(val)) f.clear(); else f.setValue(val); if (!params.isNull()) - params.append(QLatin1String(", ")); + params.append(", "_L1); params.append(driver->formatValue(f)); } return params; @@ -869,7 +833,7 @@ static QString qCreateParamString(const QList<QVariant> &boundValues, const QSql QString qMakePreparedStmtId() { - static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0); + Q_CONSTINIT static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0); QString id = QStringLiteral("qpsqlpstmt_") + QString::number(qPreparedStmtCount.fetchAndAddRelaxed(1) + 1, 16); return id; } @@ -952,7 +916,7 @@ void QPSQLDriverPrivate::setDatestyle() PGresult *result = exec("SET DATESTYLE TO 'ISO'"); int status = PQresultStatus(result); if (status != PGRES_COMMAND_OK) - qWarning("%s", PQerrorMessage(connection)); + qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection)); PQclear(result); } @@ -965,11 +929,20 @@ void QPSQLDriverPrivate::setByteaOutput() PGresult *result = exec("SET bytea_output TO escape"); int status = PQresultStatus(result); if (status != PGRES_COMMAND_OK) - qWarning("%s", PQerrorMessage(connection)); + qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection)); PQclear(result); } } +void QPSQLDriverPrivate::setUtcTimeZone() +{ + PGresult *result = exec("SET TIME ZONE 'UTC'"); + int status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK) + qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection)); + PQclear(result); +} + void QPSQLDriverPrivate::detectBackslashEscape() { // standard_conforming_strings option introduced in 8.2 @@ -981,7 +954,7 @@ void QPSQLDriverPrivate::detectBackslashEscape() PGresult *result = exec(QStringLiteral("SELECT '\\\\' x")); int status = PQresultStatus(result); if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) - if (QString::fromLatin1(PQgetvalue(result, 0, 0)) == QLatin1String("\\")) + if (QString::fromLatin1(PQgetvalue(result, 0, 0)) == "\\"_L1) hasBackslashEscape = true; PQclear(result); } @@ -1093,9 +1066,9 @@ QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion() QPSQLDriver::Protocol clientVersion = #if defined(PG_MAJORVERSION) - qFindPSQLVersion(QLatin1String(PG_MAJORVERSION)); + qFindPSQLVersion(PG_MAJORVERSION ""_L1); #elif defined(PG_VERSION) - qFindPSQLVersion(QLatin1String(PG_VERSION)); + qFindPSQLVersion(PG_VERSION ""_L1); #else QPSQLDriver::VersionUnknown; #endif @@ -1103,16 +1076,16 @@ QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion() if (serverVersion == QPSQLDriver::VersionUnknown) { serverVersion = clientVersion; if (serverVersion != QPSQLDriver::VersionUnknown) - qWarning("The server version of this PostgreSQL is unknown, falling back to the client version."); + qCWarning(lcPsql, "The server version of this PostgreSQL is unknown, " + "falling back to the client version."); } // Keep the old behavior unchanged if (serverVersion == QPSQLDriver::VersionUnknown) serverVersion = QPSQLDriver::Version6; - if (serverVersion < QPSQLDriver::Version7_3) { - qWarning("This version of PostgreSQL is not supported and may not work."); - } + if (serverVersion < QPSQLDriver::Version7_3) + qCWarning(lcPsql, "This version of PostgreSQL is not supported and may not work."); return serverVersion; } @@ -1138,8 +1111,7 @@ QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent) QPSQLDriver::~QPSQLDriver() { Q_D(QPSQLDriver); - if (d->connection) - PQfinish(d->connection); + PQfinish(d->connection); } QVariant QPSQLDriver::handle() const @@ -1159,6 +1131,7 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const case EventNotifications: case MultipleResultSets: case BLOB: + case Unicode: return true; case PreparedQueries: case PositionalPlaceholders: @@ -1169,8 +1142,6 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const case FinishQuery: case CancelQuery: return false; - case Unicode: - return d->isUtf8; } return false; } @@ -1183,9 +1154,9 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const */ static QString qQuote(QString s) { - s.replace(QLatin1Char('\\'), QLatin1String("\\\\")); - s.replace(QLatin1Char('\''), QLatin1String("\\'")); - s.append(QLatin1Char('\'')).prepend(QLatin1Char('\'')); + s.replace(u'\\', "\\\\"_L1); + s.replace(u'\'', "\\'"_L1); + s.append(u'\'').prepend(u'\''); return s; } @@ -1200,21 +1171,21 @@ bool QPSQLDriver::open(const QString &db, close(); QString connectString; if (!host.isEmpty()) - connectString.append(QLatin1String("host=")).append(qQuote(host)); + connectString.append("host="_L1).append(qQuote(host)); if (!db.isEmpty()) - connectString.append(QLatin1String(" dbname=")).append(qQuote(db)); + connectString.append(" dbname="_L1).append(qQuote(db)); if (!user.isEmpty()) - connectString.append(QLatin1String(" user=")).append(qQuote(user)); + connectString.append(" user="_L1).append(qQuote(user)); if (!password.isEmpty()) - connectString.append(QLatin1String(" password=")).append(qQuote(password)); + connectString.append(" password="_L1).append(qQuote(password)); if (port != -1) - connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port))); + connectString.append(" port="_L1).append(qQuote(QString::number(port))); // add any connect options - the server will handle error detection if (!connOpts.isEmpty()) { QString opt = connOpts; - opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive); - connectString.append(QLatin1Char(' ')).append(opt); + opt.replace(';'_L1, ' '_L1, Qt::CaseInsensitive); + connectString.append(u' ').append(opt); } d->connection = PQconnectdb(std::move(connectString).toLocal8Bit().constData()); @@ -1228,9 +1199,16 @@ bool QPSQLDriver::open(const QString &db, d->pro = d->getPSQLVersion(); d->detectBackslashEscape(); - d->isUtf8 = d->setEncodingUtf8(); + if (!d->setEncodingUtf8()) { + setLastError(qMakeError(tr("Unable to set client encoding to 'UNICODE'"), QSqlError::ConnectionError, d)); + setOpenError(true); + PQfinish(d->connection); + d->connection = nullptr; + return false; + } d->setDatestyle(); d->setByteaOutput(); + d->setUtcTimeZone(); setOpen(true); setOpenError(false); @@ -1243,13 +1221,12 @@ void QPSQLDriver::close() d->seid.clear(); if (d->sn) { - disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification())); + disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification); delete d->sn; d->sn = nullptr; } - if (d->connection) - PQfinish(d->connection); + PQfinish(d->connection); d->connection = nullptr; setOpen(false); setOpenError(false); @@ -1264,7 +1241,7 @@ bool QPSQLDriver::beginTransaction() { Q_D(QPSQLDriver); if (!isOpen()) { - qWarning("QPSQLDriver::beginTransaction: Database not open"); + qCWarning(lcPsql, "QPSQLDriver::beginTransaction: Database not open."); return false; } PGresult *res = d->exec("BEGIN"); @@ -1282,7 +1259,7 @@ bool QPSQLDriver::commitTransaction() { Q_D(QPSQLDriver); if (!isOpen()) { - qWarning("QPSQLDriver::commitTransaction: Database not open"); + qCWarning(lcPsql, "QPSQLDriver::commitTransaction: Database not open."); return false; } PGresult *res = d->exec("COMMIT"); @@ -1292,7 +1269,7 @@ bool QPSQLDriver::commitTransaction() // XXX // This hack is used to tell if the transaction has succeeded for the protocol versions of // PostgreSQL below. For 7.x and other protocol versions we are left in the dark. - // This hack can dissapear once there is an API to query this sort of information. + // This hack can disappear once there is an API to query this sort of information. if (d->pro >= QPSQLDriver::Version8) { transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0; } @@ -1311,7 +1288,7 @@ bool QPSQLDriver::rollbackTransaction() { Q_D(QPSQLDriver); if (!isOpen()) { - qWarning("QPSQLDriver::rollbackTransaction: Database not open"); + qCWarning(lcPsql, "QPSQLDriver::rollbackTransaction: Database not open."); return false; } PGresult *res = d->exec("ROLLBACK"); @@ -1335,9 +1312,9 @@ QStringList QPSQLDriver::tables(QSql::TableType type) const t.setForwardOnly(true); if (type & QSql::Tables) - const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('r')); + const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'r'); if (type & QSql::Views) - const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('v')); + const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, u'v'); if (type & QSql::SystemTables) { t.exec(QStringLiteral("SELECT relname FROM pg_class WHERE (relkind = 'r') " "AND (relname LIKE 'pg_%') ")); @@ -1350,7 +1327,7 @@ QStringList QPSQLDriver::tables(QSql::TableType type) const static void qSplitTableName(QString &tablename, QString &schema) { - int dot = tablename.indexOf(QLatin1Char('.')); + qsizetype dot = tablename.indexOf(u'.'); if (dot == -1) return; schema = tablename.left(dot); @@ -1438,8 +1415,8 @@ QSqlRecord QPSQLDriver::record(const QString &tablename) const precision = -1; } QString defVal = query.value(5).toString(); - if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) { - const int end = defVal.lastIndexOf(QLatin1Char('\'')); + if (!defVal.isEmpty() && defVal.at(0) == u'\'') { + const qsizetype end = defVal.lastIndexOf(u'\''); if (end > 0) defVal = defVal.mid(1, end - 1); } @@ -1448,7 +1425,6 @@ QSqlRecord QPSQLDriver::record(const QString &tablename) const f.setLength(len); f.setPrecision(precision); f.setDefaultValue(defVal); - f.setSqlType(query.value(1).toInt()); info.append(f); } @@ -1473,36 +1449,32 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const r = nullStr(); } else { switch (field.metaType().id()) { - case QMetaType::QDateTime: -#if QT_CONFIG(datestring) - if (field.value().toDateTime().isValid()) { + case QMetaType::QDateTime: { + const auto dt = field.value().toDateTime(); + if (dt.isValid()) { // we force the value to be considered with a timezone information, and we force it to be UTC // this is safe since postgresql stores only the UTC value and not the timezone offset (only used // while parsing), so we have correct behavior in both case of with timezone and without tz - r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + QLatin1Char('\'') + - QLocale::c().toString(field.value().toDateTime().toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") + - QLatin1Char('Z') + QLatin1Char('\''); + r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + u'\'' + + QLocale::c().toString(dt.toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") + + u'Z' + u'\''; } else { r = nullStr(); } -#else - r = nullStr(); -#endif // datestring break; - case QMetaType::QTime: -#if QT_CONFIG(datestring) - if (field.value().toTime().isValid()) { - r = QLatin1Char('\'') + field.value().toTime().toString(u"hh:mm:ss.zzz") + QLatin1Char('\''); - } else -#endif - { + } + case QMetaType::QTime: { + const auto t = field.value().toTime(); + if (t.isValid()) + r = u'\'' + QLocale::c().toString(t, u"hh:mm:ss.zzz") + u'\''; + else r = nullStr(); - } break; + } case QMetaType::QString: r = QSqlDriver::formatValue(field, trimStrings); if (d->hasBackslashEscape) - r.replace(QLatin1Char('\\'), QLatin1String("\\\\")); + r.replace(u'\\', "\\\\"_L1); break; case QMetaType::Bool: if (field.value().toBool()) @@ -1518,9 +1490,9 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const #else unsigned char *data = PQescapeBytea((const unsigned char*)ba.constData(), ba.size(), &len); #endif - r += QLatin1Char('\''); - r += QLatin1String((const char*)data); - r += QLatin1Char('\''); + r += u'\''; + r += QLatin1StringView((const char*)data); + r += u'\''; qPQfreemem(data); break; } @@ -1535,7 +1507,7 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const r = QSqlDriver::formatValue(field, trimStrings); break; case QMetaType::QUuid: - r = QLatin1Char('\'') + field.value().toString() + QLatin1Char('\''); + r = u'\'' + field.value().toString() + u'\''; break; default: r = QSqlDriver::formatValue(field, trimStrings); @@ -1548,10 +1520,10 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) { + res.replace(u'"', "\"\""_L1); + res.replace(u'.', "\".\""_L1); + res = u'"' + res + u'"'; } return res; } @@ -1572,7 +1544,7 @@ bool QPSQLDriver::subscribeToNotification(const QString &name) { Q_D(QPSQLDriver); if (!isOpen()) { - qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open."); + qCWarning(lcPsql, "QPSQLDriver::subscribeToNotification: Database not open."); return false; } @@ -1597,11 +1569,12 @@ bool QPSQLDriver::subscribeToNotification(const QString &name) PQclear(result); if (!d->sn) { - d->sn = new QSocketNotifier(socket, QSocketNotifier::Read); - connect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification())); + d->sn = new QSocketNotifier(socket, QSocketNotifier::Read, this); + connect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification); } } else { - qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on"); + qCWarning(lcPsql, "QPSQLDriver::subscribeToNotificationImplementation: " + "PQsocket didn't return a valid socket to listen on."); return false; } @@ -1612,13 +1585,13 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name) { Q_D(QPSQLDriver); if (!isOpen()) { - qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open."); + qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: Database not open."); return false; } if (!d->seid.contains(name)) { - qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.", - qPrintable(name)); + qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: not subscribed to '%ls'.", + qUtf16Printable(name)); return false; } @@ -1634,7 +1607,7 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name) d->seid.removeAll(name); if (d->seid.isEmpty()) { - disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification())); + disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification); delete d->sn; d->sn = nullptr; } @@ -1656,22 +1629,24 @@ void QPSQLDriver::_q_handleNotification() PGnotify *notify = nullptr; while ((notify = PQnotifies(d->connection)) != nullptr) { - QString name(QLatin1String(notify->relname)); + QString name(QLatin1StringView(notify->relname)); if (d->seid.contains(name)) { QString payload; #if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 70400 if (notify->extra) - payload = d->isUtf8 ? QString::fromUtf8(notify->extra) : QString::fromLatin1(notify->extra); + payload = QString::fromUtf8(notify->extra); #endif QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource; emit notification(name, source, payload); } else - qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.", - qPrintable(name)); + qCWarning(lcPsql, "QPSQLDriver: received notification for '%ls' which isn't subscribed to.", + qUtf16Printable(name)); qPQfreemem(notify); } } QT_END_NAMESPACE + +#include "moc_qsql_psql_p.cpp" diff --git a/src/plugins/sqldrivers/psql/qsql_psql_p.h b/src/plugins/sqldrivers/psql/qsql_psql_p.h index 22c0761667..4ff83bee21 100644 --- a/src/plugins/sqldrivers/psql/qsql_psql_p.h +++ b/src/plugins/sqldrivers/psql/qsql_psql_p.h @@ -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 #ifndef QSQL_PSQL_H #define QSQL_PSQL_H diff --git a/src/plugins/sqldrivers/qt_cmdline.cmake b/src/plugins/sqldrivers/qt_cmdline.cmake index 91b6f9f767..2bb1fc64eb 100644 --- a/src/plugins/sqldrivers/qt_cmdline.cmake +++ b/src/plugins/sqldrivers/qt_cmdline.cmake @@ -1,6 +1,9 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_commandline_option(mysql_config TYPE string) qt_commandline_option(psql_config TYPE string) -qt_commandline_option(sqlite TYPE enum NAME system-sqlite MAPPING qt no system yes) +qt_commandline_option(sqlite CONTROLS_FEATURE TYPE enum NAME system-sqlite MAPPING qt no system yes) qt_commandline_option(sql-db2 TYPE boolean) qt_commandline_option(sql-ibase TYPE boolean) qt_commandline_option(sql-mysql TYPE boolean) @@ -8,6 +11,7 @@ qt_commandline_option(sql-oci TYPE boolean) qt_commandline_option(sql-odbc TYPE boolean) qt_commandline_option(sql-psql TYPE boolean) qt_commandline_option(sql-sqlite TYPE boolean) +qt_commandline_option(sql-mimer TYPE boolean) qt_commandline_option(plugin-sql-db2 TYPE void NAME sql-db2) qt_commandline_option(plugin-sql-ibase TYPE void NAME sql-ibase) qt_commandline_option(plugin-sql-mysql TYPE void NAME sql-mysql) @@ -15,4 +19,4 @@ qt_commandline_option(plugin-sql-oci TYPE void NAME sql-oci) qt_commandline_option(plugin-sql-odbc TYPE void NAME sql-odbc) qt_commandline_option(plugin-sql-psql TYPE void NAME sql-psql) qt_commandline_option(plugin-sql-sqlite TYPE void NAME sql-sqlite) -qt_commandline_assignment(MYSQL_PATH mysql.prefix) +qt_commandline_option(plugin-sql-mimer TYPE void NAME sql-mimer) diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt index 1fba18290b..4203a5c437 100644 --- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt +++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from sqlite.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QSQLiteDriverPlugin Plugin: @@ -9,20 +10,18 @@ qt_internal_add_plugin(QSQLiteDriverPlugin PLUGIN_TYPE sqldrivers SOURCES qsql_sqlite.cpp qsql_sqlite_p.h + qsql_sqlite_vfs.cpp qsql_sqlite_vfs_p.h smain.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + QT_NO_CONTEXTLESS_CONNECT LIBRARIES Qt::Core Qt::CorePrivate Qt::SqlPrivate ) -#### Keys ignored in scope 1:.:.:sqlite.pro:<TRUE>: -# OTHER_FILES = "sqlite.json" -# QT_FOR_CONFIG = "sqldrivers-private" - ## Scopes: ##################################################################### @@ -31,7 +30,6 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite SQLite::SQLite3 ) -# special case begin if (NOT QT_FEATURE_system_sqlite) # On newer compilers compiling sqlite.c produces warnings qt_disable_warnings(QSQLiteDriverPlugin) @@ -40,7 +38,11 @@ endif() if(QT_FEATURE_system_sqlite) qt_internal_force_macos_intel_arch(QSQLiteDriverPlugin) endif() -# special case end + +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite AND VXWORKS + DEFINES + SQLITE_OS_UNIX=1 +) qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite SOURCES @@ -49,8 +51,11 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sq SQLITE_ENABLE_COLUMN_METADATA SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS + SQLITE_ENABLE_FTS4 SQLITE_ENABLE_FTS5 + SQLITE_ENABLE_GEOPOLY SQLITE_ENABLE_JSON1 + SQLITE_ENABLE_MATH_FUNCTIONS SQLITE_ENABLE_RTREE SQLITE_OMIT_COMPLETE INCLUDE_DIRECTORIES @@ -95,3 +100,8 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_dlopen AN DEFINES SQLITE_OMIT_LOAD_EXTENSION ) + +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_thread AND NOT QT_FEATURE_system_sqlite + DEFINES + SQLITE_THREADSAFE=0 +) diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp index d13fb1787c..c574772fd7 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.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_sqlite_p.h" @@ -43,6 +7,7 @@ #include <qdatetime.h> #include <qdebug.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qsqlerror.h> #include <qsqlfield.h> #include <qsqlindex.h> @@ -74,38 +39,24 @@ Q_DECLARE_METATYPE(sqlite3_stmt*) QT_BEGIN_NAMESPACE -static QString _q_escapeIdentifier(const QString &identifier, QSqlDriver::IdentifierType type) -{ - QString res = identifier; - // If it contains [ and ] then we assume it to be escaped properly already as this indicates - // the syntax is exactly how it should be - if (identifier.contains(QLatin1Char('[')) && identifier.contains(QLatin1Char(']'))) - return res; - if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - if (type == QSqlDriver::TableName) - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); - } - return res; -} +static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite") + +using namespace Qt::StringLiterals; static int qGetColumnType(const QString &tpName) { const QString typeName = tpName.toLower(); - if (typeName == QLatin1String("integer") - || typeName == QLatin1String("int")) + if (typeName == "integer"_L1 || typeName == "int"_L1) return QMetaType::Int; - if (typeName == QLatin1String("double") - || typeName == QLatin1String("float") - || typeName == QLatin1String("real") - || typeName.startsWith(QLatin1String("numeric"))) + if (typeName == "double"_L1 + || typeName == "float"_L1 + || typeName == "real"_L1 + || typeName.startsWith("numeric"_L1)) return QMetaType::Double; - if (typeName == QLatin1String("blob")) + if (typeName == "blob"_L1) return QMetaType::QByteArray; - if (typeName == QLatin1String("boolean") - || typeName == QLatin1String("bool")) + if (typeName == "boolean"_L1 || typeName == "bool"_L1) return QMetaType::Bool; return QMetaType::QString; } @@ -150,11 +101,64 @@ class QSQLiteDriverPrivate : public QSqlDriverPrivate public: inline QSQLiteDriverPrivate() : QSqlDriverPrivate(QSqlDriver::SQLite) {} + bool isIdentifierEscaped(QStringView identifier) const; + QSqlIndex getTableInfo(QSqlQuery &query, const QString &tableName, + bool onlyPIndex = false) const; + sqlite3 *access = nullptr; QList<QSQLiteResult *> results; QStringList notificationid; }; +bool QSQLiteDriverPrivate::isIdentifierEscaped(QStringView identifier) const +{ + return identifier.size() > 2 + && ((identifier.startsWith(u'"') && identifier.endsWith(u'"')) + || (identifier.startsWith(u'`') && identifier.endsWith(u'`')) + || (identifier.startsWith(u'[') && identifier.endsWith(u']'))); +} + +QSqlIndex QSQLiteDriverPrivate::getTableInfo(QSqlQuery &query, const QString &tableName, + bool onlyPIndex) const +{ + Q_Q(const QSQLiteDriver); + QString schema; + QString table = q->escapeIdentifier(tableName, QSqlDriver::TableName); + const auto indexOfSeparator = table.indexOf(u'.'); + if (indexOfSeparator > -1) { + auto leftName = QStringView{table}.first(indexOfSeparator); + auto rightName = QStringView{table}.sliced(indexOfSeparator + 1); + if (isIdentifierEscaped(leftName) && isIdentifierEscaped(rightName)) { + schema = leftName.toString() + u'.'; + table = rightName.toString(); + } + } + + query.exec("PRAGMA "_L1 + schema + "table_info ("_L1 + table + u')'); + QSqlIndex ind; + while (query.next()) { + bool isPk = query.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = query.value(2).toString().toLower(); + QString defVal = query.value(4).toString(); + if (!defVal.isEmpty() && defVal.at(0) == u'\'') { + const int end = defVal.lastIndexOf(u'\''); + if (end > 0) + defVal = defVal.mid(1, end - 1); + } + + QSqlField fld(query.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName); + if (isPk && (typeName == "integer"_L1)) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(query.value(3).toInt() != 0); + fld.setDefaultValue(defVal); + ind.append(fld); + } + return ind; +} class QSQLiteResultPrivate : public QSqlCachedResultPrivate { @@ -194,7 +198,7 @@ void QSQLiteResultPrivate::finalize() return; sqlite3_finalize(stmt); - stmt = 0; + stmt = nullptr; } void QSQLiteResultPrivate::initColumns(bool emptyResultset) @@ -209,10 +213,10 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset) for (int i = 0; i < nCols; ++i) { QString colName = QString(reinterpret_cast<const QChar *>( sqlite3_column_name16(stmt, i)) - ).remove(QLatin1Char('"')); + ).remove(u'"'); const QString tableName = QString(reinterpret_cast<const QChar *>( sqlite3_column_table_name16(stmt, i)) - ).remove(QLatin1Char('"')); + ).remove(u'"'); // must use typeName for resolving the type to match QSqliteDriver::record QString typeName = QString(reinterpret_cast<const QChar *>( sqlite3_column_decltype16(stmt, i))); @@ -246,7 +250,6 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset) } QSqlField fld(colName, QMetaType(fieldType), tableName); - fld.setSqlType(stp); rInf.append(fld); } } @@ -259,7 +262,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i // already fetched Q_ASSERT(!initialFetch); skipRow = false; - for(int i=0;i<firstRow.count();i++) + for(int i=0;i<firstRow.size();i++) values[i]=firstRow[i]; return skippedStatus; } @@ -418,10 +421,10 @@ bool QSQLiteResult::execBatch(bool arrayBind) Q_D(QSqlResult); QScopedValueRollback<QList<QVariant>> valuesScope(d->values); QList<QVariant> values = d->values; - if (values.count() == 0) + if (values.size() == 0) return false; - for (int i = 0; i < values.at(0).toList().count(); ++i) { + for (int i = 0; i < values.at(0).toList().size(); ++i) { d->values.clear(); QScopedValueRollback<QHash<QString, QList<int>>> indexesScope(d->indexes); auto it = d->indexes.constBegin(); @@ -455,16 +458,16 @@ bool QSQLiteResult::exec() } int paramCount = sqlite3_bind_parameter_count(d->stmt); - bool paramCountIsValid = paramCount == values.count(); + bool paramCountIsValid = paramCount == values.size(); #if (SQLITE_VERSION_NUMBER >= 3003011) // In the case of the reuse of a named placeholder // We need to check explicitly that paramCount is greater than or equal to 1, as sqlite // can end up in a case where for virtual tables it returns 0 even though it // has parameters - if (paramCount >= 1 && paramCount < values.count()) { + if (paramCount >= 1 && paramCount < values.size()) { const auto countIndexes = [](int counter, const QList<int> &indexList) { - return counter + indexList.length(); + return counter + indexList.size(); }; const int bindParamCount = std::accumulate(d->indexes.cbegin(), @@ -472,7 +475,7 @@ bool QSQLiteResult::exec() 0, countIndexes); - paramCountIsValid = bindParamCount == values.count(); + paramCountIsValid = bindParamCount == values.size(); // When using named placeholders, it will reuse the index for duplicated // placeholders. So we need to ensure the QList has only one instance of // each value as SQLite will do the rest for us. @@ -501,7 +504,7 @@ bool QSQLiteResult::exec() res = SQLITE_OK; const QVariant &value = values.at(i); - if (value.isNull()) { + if (QSqlResultPrivate::isVariantNull(value)) { res = sqlite3_bind_null(d->stmt, i + 1); } else { switch (value.userType()) { @@ -524,7 +527,7 @@ bool QSQLiteResult::exec() case QMetaType::QDateTime: { const QDateTime dateTime = value.toDateTime(); const QString str = dateTime.toString(Qt::ISODateWithMs); - res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), int(str.size() * sizeof(ushort)), SQLITE_TRANSIENT); break; @@ -532,7 +535,7 @@ bool QSQLiteResult::exec() case QMetaType::QTime: { const QTime time = value.toTime(); const QString str = time.toString(u"hh:mm:ss.zzz"); - res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), int(str.size() * sizeof(ushort)), SQLITE_TRANSIENT); break; @@ -545,9 +548,9 @@ bool QSQLiteResult::exec() SQLITE_STATIC); break; } default: { - QString str = value.toString(); + const QString str = value.toString(); // SQLITE_TRANSIENT makes sure that sqlite buffers the data - res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), int(str.size()) * sizeof(QChar), SQLITE_TRANSIENT); break; } @@ -661,6 +664,30 @@ static void _q_regexp_cleanup(void *cache) } #endif +static void _q_lower(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + if (Q_UNLIKELY(argc != 1)) { + sqlite3_result_text(context, nullptr, 0, nullptr); + return; + } + const QString lower = QString::fromUtf8( + reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toLower(); + const QByteArray ba = lower.toUtf8(); + sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT); +} + +static void _q_upper(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + if (Q_UNLIKELY(argc != 1)) { + sqlite3_result_text(context, nullptr, 0, nullptr); + return; + } + const QString upper = QString::fromUtf8( + reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toUpper(); + const QByteArray ba = upper.toUtf8(); + sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT); +} + QSQLiteDriver::QSQLiteDriver(QObject * parent) : QSqlDriver(*new QSQLiteDriverPrivate, parent) { @@ -727,38 +754,47 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c bool openReadOnlyOption = false; bool openUriOption = false; bool useExtendedResultCodes = true; + bool useQtVfs = false; + bool useQtCaseFolding = false; + bool openNoFollow = false; #if QT_CONFIG(regularexpression) - static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP"); + static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1; bool defineRegexp = false; int regexpCacheSize = 25; #endif - const auto opts = QStringView{conOpts}.split(QLatin1Char(';')); + const auto opts = QStringView{conOpts}.split(u';', Qt::SkipEmptyParts); for (auto option : opts) { option = option.trimmed(); - if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { + if (option.startsWith("QSQLITE_BUSY_TIMEOUT"_L1)) { option = option.mid(20).trimmed(); - if (option.startsWith(QLatin1Char('='))) { + if (option.startsWith(u'=')) { bool ok; const int nt = option.mid(1).trimmed().toInt(&ok); if (ok) timeOut = nt; } - } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { + } else if (option == "QSQLITE_USE_QT_VFS"_L1) { + useQtVfs = true; + } else if (option == "QSQLITE_OPEN_READONLY"_L1) { openReadOnlyOption = true; - } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { + } else if (option == "QSQLITE_OPEN_URI"_L1) { openUriOption = true; - } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { + } else if (option == "QSQLITE_ENABLE_SHARED_CACHE"_L1) { sharedCache = true; - } else if (option == QLatin1String("QSQLITE_NO_USE_EXTENDED_RESULT_CODES")) { + } else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) { useExtendedResultCodes = false; + } else if (option == "QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING"_L1) { + useQtCaseFolding = true; + } else if (option == "QSQLITE_OPEN_NOFOLLOW"_L1) { + openNoFollow = true; } #if QT_CONFIG(regularexpression) else if (option.startsWith(regexpConnectOption)) { option = option.mid(regexpConnectOption.size()).trimmed(); if (option.isEmpty()) { defineRegexp = true; - } else if (option.startsWith(QLatin1Char('='))) { + } else if (option.startsWith(u'=')) { bool ok = false; const int cacheSize = option.mid(1).trimmed().toInt(&ok); if (ok) { @@ -769,16 +805,25 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c } } #endif + else + qCWarning(lcSqlite, "Unsupported option '%ls'", qUtf16Printable(option.toString())); } int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE); if (openUriOption) openMode |= SQLITE_OPEN_URI; + if (openNoFollow) { +#if defined(SQLITE_OPEN_NOFOLLOW) + openMode |= SQLITE_OPEN_NOFOLLOW; +#else + qCWarning(lcSqlite, "SQLITE_OPEN_NOFOLLOW not supported with the SQLite version %s", sqlite3_libversion()); +#endif + } openMode |= SQLITE_OPEN_NOMUTEX; - const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, nullptr); + const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, useQtVfs ? "QtVFS" : nullptr); if (res == SQLITE_OK) { sqlite3_busy_timeout(d->access, timeOut); @@ -793,6 +838,12 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c nullptr, &_q_regexp_cleanup); } #endif + if (useQtCaseFolding) { + sqlite3_create_function_v2(d->access, "lower", 1, SQLITE_UTF8, nullptr, + &_q_lower, nullptr, nullptr, nullptr); + sqlite3_create_function_v2(d->access, "upper", 1, SQLITE_UTF8, nullptr, + &_q_upper, nullptr, nullptr, nullptr); + } return true; } else { setLastError(qMakeError(d->access, tr("Error opening database"), @@ -801,7 +852,7 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c if (d->access) { sqlite3_close(d->access); - d->access = 0; + d->access = nullptr; } return false; @@ -812,10 +863,10 @@ void QSQLiteDriver::close() { Q_D(QSQLiteDriver); if (isOpen()) { - for (QSQLiteResult *result : qAsConst(d->results)) + for (QSQLiteResult *result : std::as_const(d->results)) result->d_func()->finalize(); - if (d->access && (d->notificationid.count() > 0)) { + if (d->access && (d->notificationid.size() > 0)) { d->notificationid.clear(); sqlite3_update_hook(d->access, nullptr, nullptr); } @@ -824,7 +875,7 @@ void QSQLiteDriver::close() if (res != SQLITE_OK) setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError, res)); - d->access = 0; + d->access = nullptr; setOpen(false); setOpenError(false); } @@ -841,7 +892,7 @@ bool QSQLiteDriver::beginTransaction() return false; QSqlQuery q(createResult()); - if (!q.exec(QLatin1String("BEGIN"))) { + if (!q.exec("BEGIN"_L1)) { setLastError(QSqlError(tr("Unable to begin transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; @@ -856,7 +907,7 @@ bool QSQLiteDriver::commitTransaction() return false; QSqlQuery q(createResult()); - if (!q.exec(QLatin1String("COMMIT"))) { + if (!q.exec("COMMIT"_L1)) { setLastError(QSqlError(tr("Unable to commit transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; @@ -871,7 +922,7 @@ bool QSQLiteDriver::rollbackTransaction() return false; QSqlQuery q(createResult()); - if (!q.exec(QLatin1String("ROLLBACK"))) { + if (!q.exec("ROLLBACK"_L1)) { setLastError(QSqlError(tr("Unable to rollback transaction"), q.lastError().databaseText(), QSqlError::TransactionError)); return false; @@ -889,14 +940,14 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const QSqlQuery q(createResult()); q.setForwardOnly(true); - QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " - "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + QString sql = "SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"_L1; if ((type & QSql::Tables) && (type & QSql::Views)) - sql = sql.arg(QLatin1String("type='table' OR type='view'")); + sql = sql.arg("type='table' OR type='view'"_L1); else if (type & QSql::Tables) - sql = sql.arg(QLatin1String("type='table'")); + sql = sql.arg("type='table'"_L1); else if (type & QSql::Views) - sql = sql.arg(QLatin1String("type='view'")); + sql = sql.arg("type='view'"_L1); else sql.clear(); @@ -907,85 +958,32 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const if (type & QSql::SystemTables) { // there are no internal tables beside this one: - res.append(QLatin1String("sqlite_master")); + res.append("sqlite_master"_L1); } return res; } -static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) -{ - QString schema; - QString table(tableName); - const int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); - if (indexOfSeparator > -1) { - const int indexOfCloseBracket = tableName.indexOf(QLatin1Char(']')); - if (indexOfCloseBracket != tableName.size() - 1) { - // Handles a case like databaseName.tableName - schema = tableName.left(indexOfSeparator + 1); - table = tableName.mid(indexOfSeparator + 1); - } else { - const int indexOfOpenBracket = tableName.lastIndexOf(QLatin1Char('['), indexOfCloseBracket); - if (indexOfOpenBracket > 0) { - // Handles a case like databaseName.[tableName] - schema = tableName.left(indexOfOpenBracket); - table = tableName.mid(indexOfOpenBracket); - } - } - } - q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + - _q_escapeIdentifier(table, QSqlDriver::TableName) + QLatin1Char(')')); - QSqlIndex ind; - while (q.next()) { - bool isPk = q.value(5).toInt(); - if (onlyPIndex && !isPk) - continue; - QString typeName = q.value(2).toString().toLower(); - QString defVal = q.value(4).toString(); - if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) { - const int end = defVal.lastIndexOf(QLatin1Char('\'')); - if (end > 0) - defVal = defVal.mid(1, end - 1); - } - - QSqlField fld(q.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName); - if (isPk && (typeName == QLatin1String("integer"))) - // INTEGER PRIMARY KEY fields are auto-generated in sqlite - // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! - fld.setAutoValue(true); - fld.setRequired(q.value(3).toInt() != 0); - fld.setDefaultValue(defVal); - ind.append(fld); - } - return ind; -} - -QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tablename) const { + Q_D(const QSQLiteDriver); if (!isOpen()) return QSqlIndex(); - QString table = tblname; - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - QSqlQuery q(createResult()); q.setForwardOnly(true); - return qGetTableInfo(q, table, true); + return d->getTableInfo(q, tablename, true); } -QSqlRecord QSQLiteDriver::record(const QString &tbl) const +QSqlRecord QSQLiteDriver::record(const QString &tablename) const { + Q_D(const QSQLiteDriver); if (!isOpen()) return QSqlRecord(); - QString table = tbl; - if (isIdentifierEscaped(table, QSqlDriver::TableName)) - table = stripDelimiters(table, QSqlDriver::TableName); - QSqlQuery q(createResult()); q.setForwardOnly(true); - return qGetTableInfo(q, table); + return d->getTableInfo(q, tablename); } QVariant QSQLiteDriver::handle() const @@ -996,7 +994,52 @@ QVariant QSQLiteDriver::handle() const QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const { - return _q_escapeIdentifier(identifier, type); + Q_D(const QSQLiteDriver); + if (identifier.isEmpty() || isIdentifierEscaped(identifier, type)) + return identifier; + + const auto indexOfSeparator = identifier.indexOf(u'.'); + if (indexOfSeparator > -1) { + auto leftName = QStringView{identifier}.first(indexOfSeparator); + auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1); + const QStringView leftEnclose = d->isIdentifierEscaped(leftName) ? u"" : u"\""; + const QStringView rightEnclose = d->isIdentifierEscaped(rightName) ? u"" : u"\""; + if (leftEnclose.isEmpty() || rightEnclose.isEmpty()) + return (leftEnclose + leftName + leftEnclose + u'.' + rightEnclose + rightName + + rightEnclose); + } + return u'"' + identifier + u'"'; +} + +bool QSQLiteDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const +{ + Q_D(const QSQLiteDriver); + Q_UNUSED(type); + return d->isIdentifierEscaped(QStringView{identifier}); +} + +QString QSQLiteDriver::stripDelimiters(const QString &identifier, IdentifierType type) const +{ + Q_D(const QSQLiteDriver); + const auto indexOfSeparator = identifier.indexOf(u'.'); + if (indexOfSeparator > -1) { + auto leftName = QStringView{identifier}.first(indexOfSeparator); + auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1); + const auto leftEscaped = d->isIdentifierEscaped(leftName); + const auto rightEscaped = d->isIdentifierEscaped(rightName); + if (leftEscaped || rightEscaped) { + if (leftEscaped) + leftName = leftName.sliced(1).chopped(1); + if (rightEscaped) + rightName = rightName.sliced(1).chopped(1); + return leftName + u'.' + rightName; + } + } + + if (isIdentifierEscaped(identifier, type)) + return identifier.mid(1, identifier.size() - 2); + + return identifier; } static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, @@ -1015,18 +1058,19 @@ bool QSQLiteDriver::subscribeToNotification(const QString &name) { Q_D(QSQLiteDriver); if (!isOpen()) { - qWarning("Database not open."); + qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Database not open."); return false; } if (d->notificationid.contains(name)) { - qWarning("Already subscribing to '%s'.", qPrintable(name)); + qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Already subscribing to '%ls'.", + qUtf16Printable(name)); return false; } //sqlite supports only one notification callback, so only the first is registered d->notificationid << name; - if (d->notificationid.count() == 1) + if (d->notificationid.size() == 1) sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast<void *> (this)); return true; @@ -1036,12 +1080,13 @@ bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) { Q_D(QSQLiteDriver); if (!isOpen()) { - qWarning("Database not open."); + qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Database not open."); return false; } if (!d->notificationid.contains(name)) { - qWarning("Not subscribed to '%s'.", qPrintable(name)); + qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Not subscribed to '%ls'.", + qUtf16Printable(name)); return false; } @@ -1066,3 +1111,5 @@ void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) } QT_END_NAMESPACE + +#include "moc_qsql_sqlite_p.cpp" diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h index c7952bca9a..db6b76cb69 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h @@ -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 #ifndef QSQL_SQLITE_H #define QSQL_SQLITE_H @@ -90,9 +54,12 @@ public: QStringList tables(QSql::TableType) const override; QSqlRecord record(const QString& tablename) const override; - QSqlIndex primaryIndex(const QString &table) const override; + QSqlIndex primaryIndex(const QString &tablename) const override; QVariant handle() const override; - QString escapeIdentifier(const QString &identifier, IdentifierType) const override; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const override; + bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const override; + QString stripDelimiters(const QString &identifier, IdentifierType type) const override; bool subscribeToNotification(const QString &name) override; bool unsubscribeFromNotification(const QString &name) override; diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp new file mode 100644 index 0000000000..bbba3cd14f --- /dev/null +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp @@ -0,0 +1,258 @@ +// Copyright (C) 2023 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_sqlite_vfs_p.h" + +#include <QFile> + +#include <limits.h> // defines PATH_MAX on unix +#include <sqlite3.h> +#include <stdio.h> // defines FILENAME_MAX everywhere + +#ifndef PATH_MAX +# define PATH_MAX FILENAME_MAX +#endif +#if SQLITE_VERSION_NUMBER < 3040000 +typedef const char *sqlite3_filename; +#endif + +namespace { +struct Vfs : sqlite3_vfs { + sqlite3_vfs *pVfs; + sqlite3_io_methods ioMethods; +}; + +struct File : sqlite3_file { + class QtFile : public QFile { + public: + QtFile(const QString &name, bool removeOnClose) + : QFile(name) + , removeOnClose(removeOnClose) + {} + + ~QtFile() override + { + if (removeOnClose) + remove(); + } + private: + bool removeOnClose; + }; + QtFile *pFile; +}; + + +int xClose(sqlite3_file *sfile) +{ + auto file = static_cast<File *>(sfile); + delete file->pFile; + file->pFile = nullptr; + return SQLITE_OK; +} + +int xRead(sqlite3_file *sfile, void *ptr, int iAmt, sqlite3_int64 iOfst) +{ + auto file = static_cast<File *>(sfile); + if (!file->pFile->seek(iOfst)) + return SQLITE_IOERR_READ; + + auto sz = file->pFile->read(static_cast<char *>(ptr), iAmt); + if (sz < iAmt) { + memset(static_cast<char *>(ptr) + sz, 0, size_t(iAmt - sz)); + return SQLITE_IOERR_SHORT_READ; + } + return SQLITE_OK; +} + +int xWrite(sqlite3_file *sfile, const void *data, int iAmt, sqlite3_int64 iOfst) +{ + auto file = static_cast<File *>(sfile); + if (!file->pFile->seek(iOfst)) + return SQLITE_IOERR_SEEK; + return file->pFile->write(reinterpret_cast<const char*>(data), iAmt) == iAmt ? SQLITE_OK : SQLITE_IOERR_WRITE; +} + +int xTruncate(sqlite3_file *sfile, sqlite3_int64 size) +{ + auto file = static_cast<File *>(sfile); + return file->pFile->resize(size) ? SQLITE_OK : SQLITE_IOERR_TRUNCATE; +} + +int xSync(sqlite3_file *sfile, int /*flags*/) +{ + static_cast<File *>(sfile)->pFile->flush(); + return SQLITE_OK; +} + +int xFileSize(sqlite3_file *sfile, sqlite3_int64 *pSize) +{ + auto file = static_cast<File *>(sfile); + *pSize = file->pFile->size(); + return SQLITE_OK; +} + +// No lock/unlock for QFile, QLockFile doesn't work for me + +int xLock(sqlite3_file *, int) { return SQLITE_OK; } + +int xUnlock(sqlite3_file *, int) { return SQLITE_OK; } + +int xCheckReservedLock(sqlite3_file *, int *pResOut) +{ + *pResOut = 0; + return SQLITE_OK; +} + +int xFileControl(sqlite3_file *, int, void *) { return SQLITE_NOTFOUND; } + +int xSectorSize(sqlite3_file *) +{ + return 4096; +} + +int xDeviceCharacteristics(sqlite3_file *) +{ + return 0; // no SQLITE_IOCAP_XXX +} + +int xOpen(sqlite3_vfs *svfs, sqlite3_filename zName, sqlite3_file *sfile, + int flags, int *pOutFlags) +{ + auto vfs = static_cast<Vfs *>(svfs); + auto file = static_cast<File *>(sfile); + memset(file, 0, sizeof(File)); + QIODeviceBase::OpenMode mode = QIODeviceBase::NotOpen; + if (!zName || (flags & SQLITE_OPEN_MEMORY)) + return SQLITE_PERM; + if ((flags & SQLITE_OPEN_READONLY) && + !(flags & SQLITE_OPEN_READWRITE) && + !(flags & SQLITE_OPEN_CREATE) && + !(flags & SQLITE_OPEN_DELETEONCLOSE)) { + mode |= QIODeviceBase::OpenModeFlag::ReadOnly; + } else { + /* + ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction + ** with the [SQLITE_OPEN_CREATE] flag, which are both directly + ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() + ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the + ** SQLITE_OPEN_CREATE, is used to indicate that file should always + ** be created, and that it is an error if it already exists. + ** It is <i>not</i> used to indicate the file should be opened + ** for exclusive access. + */ + if ((flags & SQLITE_OPEN_CREATE) && (flags & SQLITE_OPEN_EXCLUSIVE)) + mode |= QIODeviceBase::OpenModeFlag::NewOnly; + + if (flags & SQLITE_OPEN_READWRITE) + mode |= QIODeviceBase::OpenModeFlag::ReadWrite; + } + + file->pMethods = &vfs->ioMethods; + file->pFile = new File::QtFile(QString::fromUtf8(zName), bool(flags & SQLITE_OPEN_DELETEONCLOSE)); + if (!file->pFile->open(mode)) + return SQLITE_CANTOPEN; + if (pOutFlags) + *pOutFlags = flags; + + return SQLITE_OK; +} + +int xDelete(sqlite3_vfs *, const char *zName, int) +{ + return QFile::remove(QString::fromUtf8(zName)) ? SQLITE_OK : SQLITE_ERROR; +} + +int xAccess(sqlite3_vfs */*svfs*/, const char *zName, int flags, int *pResOut) +{ + *pResOut = 0; + switch (flags) { + case SQLITE_ACCESS_EXISTS: + case SQLITE_ACCESS_READ: + *pResOut = QFile::exists(QString::fromUtf8(zName)); + break; + default: + break; + } + return SQLITE_OK; +} + +int xFullPathname(sqlite3_vfs *, const char *zName, int nOut, char *zOut) +{ + if (!zName) + return SQLITE_ERROR; + + int i = 0; + for (;zName[i] && i < nOut; ++i) + zOut[i] = zName[i]; + + if (i >= nOut) + return SQLITE_ERROR; + + zOut[i] = '\0'; + return SQLITE_OK; +} + +int xRandomness(sqlite3_vfs *svfs, int nByte, char *zOut) +{ + auto vfs = static_cast<Vfs *>(svfs)->pVfs; + return vfs->xRandomness(vfs, nByte, zOut); +} + +int xSleep(sqlite3_vfs *svfs, int microseconds) +{ + auto vfs = static_cast<Vfs *>(svfs)->pVfs; + return vfs->xSleep(vfs, microseconds); +} + +int xCurrentTime(sqlite3_vfs *svfs, double *zOut) +{ + auto vfs = static_cast<Vfs *>(svfs)->pVfs; + return vfs->xCurrentTime(vfs, zOut); +} + +int xGetLastError(sqlite3_vfs *, int, char *) +{ + return 0; +} + +int xCurrentTimeInt64(sqlite3_vfs *svfs, sqlite3_int64 *zOut) +{ + auto vfs = static_cast<Vfs *>(svfs)->pVfs; + return vfs->xCurrentTimeInt64(vfs, zOut); +} +} // namespace { + +void register_qt_vfs() +{ + static Vfs vfs; + memset(&vfs, 0, sizeof(Vfs)); + vfs.iVersion = 1; + vfs.szOsFile = sizeof(File); + vfs.mxPathname = PATH_MAX; + vfs.zName = "QtVFS"; + vfs.xOpen = &xOpen; + vfs.xDelete = &xDelete; + vfs.xAccess = &xAccess; + vfs.xFullPathname = &xFullPathname; + vfs.xRandomness = &xRandomness; + vfs.xSleep = &xSleep; + vfs.xCurrentTime = &xCurrentTime; + vfs.xGetLastError = &xGetLastError; + vfs.xCurrentTimeInt64 = &xCurrentTimeInt64; + vfs.pVfs = sqlite3_vfs_find(nullptr); + vfs.ioMethods.iVersion = 1; + vfs.ioMethods.xClose = &xClose; + vfs.ioMethods.xRead = &xRead; + vfs.ioMethods.xWrite = &xWrite; + vfs.ioMethods.xTruncate = &xTruncate; + vfs.ioMethods.xSync = &xSync; + vfs.ioMethods.xFileSize = &xFileSize; + vfs.ioMethods.xLock = &xLock; + vfs.ioMethods.xUnlock = &xUnlock; + vfs.ioMethods.xCheckReservedLock = &xCheckReservedLock; + vfs.ioMethods.xFileControl = &xFileControl; + vfs.ioMethods.xSectorSize = &xSectorSize; + vfs.ioMethods.xDeviceCharacteristics = &xDeviceCharacteristics; + + sqlite3_vfs_register(&vfs, 0); +} diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h new file mode 100644 index 0000000000..56024b3ecb --- /dev/null +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h @@ -0,0 +1,21 @@ +// Copyright (C) 2023 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 + +#ifndef QSQL_SQLITE_VFS_H +#define QSQL_SQLITE_VFS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +void register_qt_vfs(); + + +#endif // QSQL_SQLITE_VFS_H diff --git a/src/plugins/sqldrivers/sqlite/smain.cpp b/src/plugins/sqldrivers/sqlite/smain.cpp index 092813990c..0d201c38d3 100644 --- a/src/plugins/sqldrivers/sqlite/smain.cpp +++ b/src/plugins/sqldrivers/sqlite/smain.cpp @@ -1,48 +1,15 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 <qsqldriverplugin.h> #include <qstringlist.h> #include "qsql_sqlite_p.h" +#include "qsql_sqlite_vfs_p.h" QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QSQLiteDriverPlugin : public QSqlDriverPlugin { Q_OBJECT @@ -57,15 +24,17 @@ public: QSQLiteDriverPlugin::QSQLiteDriverPlugin() : QSqlDriverPlugin() { + register_qt_vfs(); } QSqlDriver* QSQLiteDriverPlugin::create(const QString &name) { - if (name == QLatin1String("QSQLITE")) { + if (name == "QSQLITE"_L1) { QSQLiteDriver* driver = new QSQLiteDriver(); return driver; } - return 0; + + return nullptr; } QT_END_NAMESPACE |