diff options
Diffstat (limited to 'src/plugins/sqldrivers/sqlite')
-rw-r--r-- | src/plugins/sqldrivers/sqlite/.prev_CMakeLists.txt | 85 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/CMakeLists.txt | 59 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp | 449 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h | 47 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp | 258 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h | 21 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/smain.cpp | 49 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/sqlite.pro | 18 |
8 files changed, 591 insertions, 395 deletions
diff --git a/src/plugins/sqldrivers/sqlite/.prev_CMakeLists.txt b/src/plugins/sqldrivers/sqlite/.prev_CMakeLists.txt deleted file mode 100644 index 25796d9bb5..0000000000 --- a/src/plugins/sqldrivers/sqlite/.prev_CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Generated from sqlite.pro. - -##################################################################### -## QSQLiteDriverPlugin Plugin: -##################################################################### - -qt_internal_add_plugin(QSQLiteDriverPlugin - OUTPUT_NAME qsqlite - TYPE sqldrivers - SOURCES - qsql_sqlite.cpp qsql_sqlite_p.h - smain.cpp - DEFINES - QT_NO_CAST_FROM_ASCII - QT_NO_CAST_TO_ASCII - PUBLIC_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: -##################################################################### - -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite - PUBLIC_LIBRARIES - SQLite::SQLite3 -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite - SOURCES - ../../../3rdparty/sqlite/sqlite3.c - DEFINES - SQLITE_ENABLE_COLUMN_METADATA - SQLITE_ENABLE_FTS3 - SQLITE_ENABLE_FTS3_PARENTHESIS - SQLITE_ENABLE_FTS5 - SQLITE_ENABLE_JSON1 - SQLITE_ENABLE_RTREE - SQLITE_OMIT_COMPLETE - INCLUDE_DIRECTORIES - ../../../3rdparty/sqlite -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION CMAKE_BUILD_TYPE STREQUAL Release AND NOT QT_FEATURE_system_sqlite - DEFINES - NDEBUG -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_largefile AND NOT QT_FEATURE_system_sqlite - DEFINES - SQLITE_DISABLE_LFS -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_posix_fallocate AND NOT QT_FEATURE_system_sqlite - DEFINES - HAVE_POSIX_FALLOCATE=1 -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION QNX AND NOT QT_FEATURE_system_sqlite - DEFINES - _QNX_SOURCE -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION UNIX AND NOT QT_FEATURE_system_sqlite - DEFINES - HAVE_USLEEP=1 -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite - PUBLIC_LIBRARIES - ${CMAKE_DL_LIBS} -) - -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite - DEFINES - SQLITE_OMIT_LOAD_EXTENSION -) - -#### Keys ignored in scope 12:.:../../../3rdparty:../../../3rdparty/sqlite.pri:INTEGRITY: -# QMAKE_CFLAGS = "-include" "qplatformdefs.h" diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt index 154516b5ff..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: @@ -6,87 +7,101 @@ qt_internal_add_plugin(QSQLiteDriverPlugin OUTPUT_NAME qsqlite - TYPE sqldrivers + 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 - PUBLIC_LIBRARIES + 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: ##################################################################### -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite - PUBLIC_LIBRARIES +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite + LIBRARIES SQLite::SQLite3 ) -# special case begin if (NOT QT_FEATURE_system_sqlite) # On newer compilers compiling sqlite.c produces warnings qt_disable_warnings(QSQLiteDriverPlugin) endif() -# special case end -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite +if(QT_FEATURE_system_sqlite) + qt_internal_force_macos_intel_arch(QSQLiteDriverPlugin) +endif() + +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 ../../../3rdparty/sqlite/sqlite3.c DEFINES 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 ../../../3rdparty/sqlite ) -qt_extend_target(QSQLiteDriverPlugin CONDITION CMAKE_BUILD_TYPE STREQUAL Release AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION CMAKE_BUILD_TYPE STREQUAL Release AND NOT QT_FEATURE_system_sqlite DEFINES NDEBUG ) -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_largefile AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_largefile AND NOT QT_FEATURE_system_sqlite DEFINES SQLITE_DISABLE_LFS ) -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_posix_fallocate AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_posix_fallocate AND NOT QT_FEATURE_system_sqlite DEFINES HAVE_POSIX_FALLOCATE=1 ) -qt_extend_target(QSQLiteDriverPlugin CONDITION QNX AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QNX AND NOT QT_FEATURE_system_sqlite DEFINES _QNX_SOURCE ) -qt_extend_target(QSQLiteDriverPlugin CONDITION UNIX AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION UNIX AND NOT QT_FEATURE_system_sqlite DEFINES HAVE_USLEEP=1 ) -qt_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite - PUBLIC_LIBRARIES +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION INTEGRITY + COMPILE_OPTIONS -include qplatformdefs.h +) + +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite + LIBRARIES ${CMAKE_DL_LIBS} ) -qt_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_dlopen AND NOT QT_FEATURE_system_sqlite DEFINES SQLITE_OMIT_LOAD_EXTENSION ) -#### Keys ignored in scope 12:.:../../../3rdparty:../../../3rdparty/sqlite.pri:INTEGRITY: -# QMAKE_CFLAGS = "-include" "qplatformdefs.h" +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 60e871a25a..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,35 +39,26 @@ Q_DECLARE_METATYPE(sqlite3_stmt*) QT_BEGIN_NAMESPACE -static QString _q_escapeIdentifier(const QString &identifier) -{ - 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("\".\"")); - } - return res; -} +static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite") + +using namespace Qt::StringLiterals; -static QVariant::Type qGetColumnType(const QString &tpName) +static int qGetColumnType(const QString &tpName) { const QString typeName = tpName.toLower(); - if (typeName == QLatin1String("integer") - || typeName == QLatin1String("int")) - return QVariant::Int; - if (typeName == QLatin1String("double") - || typeName == QLatin1String("float") - || typeName == QLatin1String("real") - || typeName.startsWith(QLatin1String("numeric"))) - return QVariant::Double; - if (typeName == QLatin1String("blob")) - return QVariant::ByteArray; - if (typeName == QLatin1String("boolean") - || typeName == QLatin1String("bool")) - return QVariant::Bool; - return QVariant::String; + if (typeName == "integer"_L1 || typeName == "int"_L1) + return QMetaType::Int; + if (typeName == "double"_L1 + || typeName == "float"_L1 + || typeName == "real"_L1 + || typeName.startsWith("numeric"_L1)) + return QMetaType::Double; + if (typeName == "blob"_L1) + return QMetaType::QByteArray; + if (typeName == "boolean"_L1 || typeName == "bool"_L1) + return QMetaType::Bool; + return QMetaType::QString; } static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, @@ -145,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 { @@ -189,7 +198,7 @@ void QSQLiteResultPrivate::finalize() return; sqlite3_finalize(stmt); - stmt = 0; + stmt = nullptr; } void QSQLiteResultPrivate::initColumns(bool emptyResultset) @@ -204,17 +213,17 @@ 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))); // sqlite3_column_type is documented to have undefined behavior if the result set is empty int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); - QVariant::Type fieldType; + int fieldType; if (!typeName.isEmpty()) { fieldType = qGetColumnType(typeName); @@ -222,26 +231,25 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset) // Get the proper type for the field based on stp value switch (stp) { case SQLITE_INTEGER: - fieldType = QVariant::Int; + fieldType = QMetaType::Int; break; case SQLITE_FLOAT: - fieldType = QVariant::Double; + fieldType = QMetaType::Double; break; case SQLITE_BLOB: - fieldType = QVariant::ByteArray; + fieldType = QMetaType::QByteArray; break; case SQLITE_TEXT: - fieldType = QVariant::String; + fieldType = QMetaType::QString; break; case SQLITE_NULL: default: - fieldType = QVariant::Invalid; + fieldType = QMetaType::UnknownType; break; } } - QSqlField fld(colName, fieldType, tableName); - fld.setSqlType(stp); + QSqlField fld(colName, QMetaType(fieldType), tableName); rInf.append(fld); } } @@ -249,20 +257,18 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset) bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) { Q_Q(QSQLiteResult); - int res; - int i; if (skipRow) { // 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; } skipRow = initialFetch; - if(initialFetch) { + if (initialFetch) { firstRow.clear(); firstRow.resize(sqlite3_column_count(stmt)); } @@ -273,8 +279,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i q->setAt(QSql::AfterLastRow); return false; } - res = sqlite3_step(stmt); - + int res = sqlite3_step(stmt); switch(res) { case SQLITE_ROW: // check to see if should fill out columns @@ -283,7 +288,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i initColumns(false); if (idx < 0 && !initialFetch) return true; - for (i = 0; i < rInf.count(); ++i) { + for (int i = 0; i < rInf.count(); ++i) { switch (sqlite3_column_type(stmt, i)) { case SQLITE_BLOB: values[i + idx] = QByteArray(static_cast<const char *>( @@ -385,13 +390,14 @@ bool QSQLiteResult::prepare(const QString &query) setSelect(false); - const void *pzTail = NULL; + const void *pzTail = nullptr; + const auto size = int((query.size() + 1) * sizeof(QChar)); #if (SQLITE_VERSION_NUMBER >= 3003011) - int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), + int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), size, &d->stmt, &pzTail); #else - int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + int res = sqlite3_prepare16(d->access, query.constData(), size, &d->stmt, &pzTail); #endif @@ -415,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(); @@ -452,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(), @@ -469,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. @@ -496,53 +502,57 @@ bool QSQLiteResult::exec() if (paramCountIsValid) { for (int i = 0; i < paramCount; ++i) { res = SQLITE_OK; - const QVariant value = values.at(i); + 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()) { - case QVariant::ByteArray: { + case QMetaType::QByteArray: { const QByteArray *ba = static_cast<const QByteArray*>(value.constData()); res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), ba->size(), SQLITE_STATIC); break; } - case QVariant::Int: - case QVariant::Bool: + case QMetaType::Int: + case QMetaType::Bool: res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); break; - case QVariant::Double: + case QMetaType::Double: res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); break; - case QVariant::UInt: - case QVariant::LongLong: + case QMetaType::UInt: + case QMetaType::LongLong: res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); break; - case QVariant::DateTime: { + 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(), - str.size() * sizeof(ushort), SQLITE_TRANSIENT); + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), + int(str.size() * sizeof(ushort)), + SQLITE_TRANSIENT); break; } - case QVariant::Time: { + 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(), - str.size() * sizeof(ushort), SQLITE_TRANSIENT); + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), + int(str.size() * sizeof(ushort)), + SQLITE_TRANSIENT); break; } - case QVariant::String: { + case QMetaType::QString: { // lifetime of string == lifetime of its qvariant const QString *str = static_cast<const QString*>(value.constData()); - res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), - (str->size()) * sizeof(QChar), SQLITE_STATIC); + res = sqlite3_bind_text16(d->stmt, i + 1, str->unicode(), + int(str->size()) * sizeof(QChar), + 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(), - (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + res = sqlite3_bind_text16(d->stmt, i + 1, str.data(), + int(str.size()) * sizeof(QChar), + SQLITE_TRANSIENT); break; } } } @@ -654,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) { @@ -719,36 +753,48 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c bool sharedCache = false; 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 == "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) { @@ -759,28 +805,45 @@ 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, NULL); + 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); + sqlite3_extended_result_codes(d->access, useExtendedResultCodes); setOpen(true); setOpenError(false); #if QT_CONFIG(regularexpression) if (defineRegexp) { auto cache = new QCache<QString, QRegularExpression>(regexpCacheSize); - sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, &_q_regexp, NULL, - NULL, &_q_regexp_cleanup); + sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, + &_q_regexp, nullptr, + 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"), @@ -789,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; @@ -800,19 +863,19 @@ 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, NULL, NULL); + sqlite3_update_hook(d->access, nullptr, nullptr); } const int res = sqlite3_close(d->access); 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); } @@ -829,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; @@ -844,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; @@ -859,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; @@ -877,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(); @@ -895,74 +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); - int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); - if (indexOfSeparator > -1) { - schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); - table = tableName.mid(indexOfSeparator + 1); - } - q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + 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(), 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 @@ -973,8 +994,52 @@ QVariant QSQLiteDriver::handle() const QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const { + 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 _q_escapeIdentifier(identifier); + 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, @@ -993,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; @@ -1014,18 +1080,19 @@ 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; } d->notificationid.removeAll(name); if (d->notificationid.isEmpty()) - sqlite3_update_hook(d->access, NULL, NULL); + sqlite3_update_hook(d->access, nullptr, nullptr); return true; } @@ -1044,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 diff --git a/src/plugins/sqldrivers/sqlite/sqlite.pro b/src/plugins/sqldrivers/sqlite/sqlite.pro deleted file mode 100644 index d7e19f97b1..0000000000 --- a/src/plugins/sqldrivers/sqlite/sqlite.pro +++ /dev/null @@ -1,18 +0,0 @@ -TARGET = qsqlite - -HEADERS += $$PWD/qsql_sqlite_p.h -SOURCES += $$PWD/qsql_sqlite.cpp $$PWD/smain.cpp - -include($$OUT_PWD/../qtsqldrivers-config.pri) -QT_FOR_CONFIG += sqldrivers-private - -qtConfig(system-sqlite) { - QMAKE_USE += sqlite -} else { - include($$PWD/../../../3rdparty/sqlite.pri) -} - -OTHER_FILES += sqlite.json - -PLUGIN_CLASS_NAME = QSQLiteDriverPlugin -include(../qsqldriverbase.pri) |