diff options
Diffstat (limited to 'src/plugins/sqldrivers/sqlite')
-rw-r--r-- | src/plugins/sqldrivers/sqlite/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp | 379 | ||||
-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 |
6 files changed, 525 insertions, 253 deletions
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 |