diff options
Diffstat (limited to 'src/plugins/sqldrivers/sqlite')
-rw-r--r-- | src/plugins/sqldrivers/sqlite/CMakeLists.txt | 15 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp | 247 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h | 7 | ||||
-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 | 2 |
6 files changed, 457 insertions, 93 deletions
diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt index a183ecc965..4203a5c437 100644 --- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt +++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt @@ -1,8 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from sqlite.pro. - ##################################################################### ## QSQLiteDriverPlugin Plugin: ##################################################################### @@ -12,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: ##################################################################### @@ -34,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) @@ -43,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 diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp index 6812addae1..c574772fd7 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp @@ -7,6 +7,7 @@ #include <qdatetime.h> #include <qdebug.h> #include <qlist.h> +#include <qloggingcategory.h> #include <qsqlerror.h> #include <qsqlfield.h> #include <qsqlindex.h> @@ -38,23 +39,9 @@ Q_DECLARE_METATYPE(sqlite3_stmt*) QT_BEGIN_NAMESPACE -using namespace Qt::StringLiterals; +static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite") -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(u'[') && identifier.contains(u']')) - return res; - if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) { - res.replace(u'"', "\"\""_L1); - res.prepend(u'"').append(u'"'); - if (type == QSqlDriver::TableName) - res.replace(u'.', "\".\""_L1); - } - return res; -} +using namespace Qt::StringLiterals; static int qGetColumnType(const QString &tpName) { @@ -114,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 { @@ -210,7 +250,6 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset) } QSqlField fld(colName, QMetaType(fieldType), tableName); - fld.setSqlType(stp); rInf.append(fld); } } @@ -625,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) { @@ -691,13 +754,16 @@ 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 auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1; bool defineRegexp = false; int regexpCacheSize = 25; #endif - const auto opts = QStringView{conOpts}.split(u';'); + const auto opts = QStringView{conOpts}.split(u';', Qt::SkipEmptyParts); for (auto option : opts) { option = option.trimmed(); if (option.startsWith("QSQLITE_BUSY_TIMEOUT"_L1)) { @@ -708,6 +774,8 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c if (ok) timeOut = nt; } + } else if (option == "QSQLITE_USE_QT_VFS"_L1) { + useQtVfs = true; } else if (option == "QSQLITE_OPEN_READONLY"_L1) { openReadOnlyOption = true; } else if (option == "QSQLITE_OPEN_URI"_L1) { @@ -716,6 +784,10 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c 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)) { @@ -733,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); @@ -757,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"), @@ -877,79 +964,26 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const return res; } -static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) -{ - QString schema; - QString table(tableName); - const qsizetype indexOfSeparator = tableName.indexOf(u'.'); - if (indexOfSeparator > -1) { - const qsizetype indexOfCloseBracket = tableName.indexOf(u']'); - if (indexOfCloseBracket != tableName.size() - 1) { - // Handles a case like databaseName.tableName - schema = tableName.left(indexOfSeparator + 1); - table = tableName.mid(indexOfSeparator + 1); - } else { - const qsizetype indexOfOpenBracket = tableName.lastIndexOf(u'[', indexOfCloseBracket); - if (indexOfOpenBracket > 0) { - // Handles a case like databaseName.[tableName] - schema = tableName.left(indexOfOpenBracket); - table = tableName.mid(indexOfOpenBracket); - } - } - } - q.exec("PRAGMA "_L1 + schema + "table_info ("_L1 + - _q_escapeIdentifier(table, QSqlDriver::TableName) + u')'); - 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) == u'\'') { - const int end = defVal.lastIndexOf(u'\''); - if (end > 0) - defVal = defVal.mid(1, end - 1); - } - - QSqlField fld(q.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(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 @@ -960,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, @@ -979,12 +1058,13 @@ 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; } @@ -1000,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; } diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h index 53ffb45f96..db6b76cb69 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h @@ -54,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 f84a256bc8..0d201c38d3 100644 --- a/src/plugins/sqldrivers/sqlite/smain.cpp +++ b/src/plugins/sqldrivers/sqlite/smain.cpp @@ -4,6 +4,7 @@ #include <qsqldriverplugin.h> #include <qstringlist.h> #include "qsql_sqlite_p.h" +#include "qsql_sqlite_vfs_p.h" QT_BEGIN_NAMESPACE @@ -23,6 +24,7 @@ public: QSQLiteDriverPlugin::QSQLiteDriverPlugin() : QSqlDriverPlugin() { + register_qt_vfs(); } QSqlDriver* QSQLiteDriverPlugin::create(const QString &name) |