diff --git a/src/3rdparty/sqlite/sqlite_cfg.h b/src/3rdparty/sqlite/sqlite_cfg.h index 66a8e9b3..1a12ca75 100644 --- a/src/3rdparty/sqlite/sqlite_cfg.h +++ b/src/3rdparty/sqlite/sqlite_cfg.h @@ -31,12 +31,12 @@ #define HAVE_MALLOC_USABLE_SIZE 1 #define HAVE_ISNAN 1 -//#define SQLITE_ENABLE_FTS5 1 +#define SQLITE_ENABLE_FTS5 1 //#define SQLITE_ENABLE_JSON1 1 #define SQLITE_ENABLE_MEMORY_MANAGEMENT 1 //#define SQLITE_ENABLE_NULL_TRIM 1 //#define SQLITE_ENABLE_PREUPDATE_HOOK 1 -//#define SQLITE_ENABLE_SESSION 1 +#define SQLITE_ENABLE_SESSION 1 #define SQLITE_ENABLE_STAT4 1 //#define SQLITE_ENABLE_UNLOCK_NOTIFY 1 @@ -62,7 +62,7 @@ #define SQLITE_OMIT_AUTOINIT 1 #define SQLITE_OMIT_AUTOMATIC_INDEX 1 #define SQLITE_OMIT_AUTORESET 1 -#define SQLITE_OMIT_BLOB_LITERAL 1 +//#define SQLITE_OMIT_BLOB_LITERAL 1 #define SQLITE_OMIT_CAST 1 #define SQLITE_OMIT_CHECK 1 #define SQLITE_OMIT_COMPLETE 1 @@ -71,7 +71,7 @@ #define SQLITE_OMIT_EXPLAIN 1 #define SQLITE_OMIT_FOREIGN_KEY 1 #define SQLITE_OMIT_GET_TABLE 1 -#define SQLITE_OMIT_INCRBLOB 1 +//#define SQLITE_OMIT_INCRBLOB 1 #define SQLITE_OMIT_INTEGRITY_CHECK 1 #define SQLITE_OMIT_JSON 1 #define SQLITE_OMIT_LOAD_EXTENSION 1 diff --git a/src/ui/3rdparty/sqlite/sqlitedb.cpp b/src/ui/3rdparty/sqlite/sqlitedb.cpp index 7c377235..a2501869 100644 --- a/src/ui/3rdparty/sqlite/sqlitedb.cpp +++ b/src/ui/3rdparty/sqlite/sqlitedb.cpp @@ -19,6 +19,8 @@ const char *const defaultSqlPragmas = "PRAGMA journal_mode = WAL;" "PRAGMA synchronous = NORMAL;" "PRAGMA encoding = 'UTF-8';"; +const QString ftsTableSuffix = "_fts"; + QAtomicInt g_sqliteInitCount; bool removeDbFile(const QString &filePath) @@ -45,6 +47,12 @@ bool renameDbFile(const QString &filePath, const QString &newFilePath) return true; } +QString makeTriggerColumnNames( + const QString &rowIdName, const QStringList &columnNames, const QString &prefix) +{ + return (prefix + rowIdName) + (',' + prefix) + columnNames.join(',' + prefix); +} + } SqliteDb::SqliteDb(const QString &filePath, quint32 openFlags) : @@ -248,6 +256,11 @@ bool SqliteDb::setBusyTimeoutMs(int v) return sqlite3_busy_timeout(m_db, v) == SQLITE_OK; } +QString SqliteDb::getFtsTableName(const QString &tableName) +{ + return tableName + ftsTableSuffix; +} + QString SqliteDb::migrationOldSchemaName() { return QLatin1String("old"); @@ -269,8 +282,10 @@ QStringList SqliteDb::tableNames(const QString &schemaName) const auto masterTable = entityName(schemaName, "sqlite_master"); const auto sql = QString("SELECT name FROM %1" - " WHERE type = 'table' AND name NOT LIKE 'sqlite_%';") - .arg(masterTable); + " WHERE type = 'table'" + " AND name NOT LIKE 'sqlite_%'" + " AND name NOT LIKE '%%2_%';") + .arg(masterTable, ftsTableSuffix); SqliteStmt stmt; if (stmt.prepare(db(), sql.toLatin1())) { @@ -353,14 +368,14 @@ bool SqliteDb::migrateDb(const MigrateOptions &opt, int userVersion, bool isNewD } // Run migration SQL scripts - bool success = migrateSqlScripts(opt, userVersion, isNewDb); + if (!migrateSqlScripts(opt, userVersion, isNewDb)) + return false; // Re-create the DB: End - if (success && opt.recreate) { - success = importBackup(opt); - } + if (opt.recreate && !(createFtsTables(opt) && importBackup(opt))) + return false; - return success; + return true; } bool SqliteDb::migrateSqlScripts(const MigrateOptions &opt, int userVersion, bool isNewDb) @@ -415,6 +430,74 @@ bool SqliteDb::migrateSqlScripts(const MigrateOptions &opt, int userVersion, boo return success; } +bool SqliteDb::createFtsTables(const MigrateOptions &opt) +{ + if (opt.ftsTables.isEmpty()) + return true; + + bool success = true; + + beginTransaction(); + + for (const FtsTable &ftsTable : opt.ftsTables) { + beginSavepoint(); + + success = createFtsTable(ftsTable); + + if (success) { + releaseSavepoint(); + } else { + qCCritical(LC) << "FTS error:" << ftsTable.contentTable << errorMessage(); + rollbackSavepoint(); + break; + } + } + + commitTransaction(); + + return success; +} + +bool SqliteDb::createFtsTable(const FtsTable &ftsTable) +{ + /* + * %1: content table name + * %2: fts table name + * %3: content rowid column name + * %4: content column names list + * %5: triggered new column names list (new.*) + * %6: triggered old column names list (old.*) + */ + static const char *const ftsCreateSql = + "CREATE VIRTUAL TABLE %2 USING fts5(%4, content='%1', content_rowid='%3');" + "CREATE TRIGGER %2_ai AFTER INSERT ON %1 BEGIN" + " INSERT INTO %2(rowid, %4) VALUES (%5);" + "END;" + "CREATE TRIGGER %2_ad AFTER DELETE ON %1 BEGIN" + " INSERT INTO %2(%2, rowid, %4) VALUES('delete', %6);" + "END;" + "CREATE TRIGGER %2_au AFTER UPDATE ON %1 BEGIN" + " INSERT INTO %2(%2, rowid, %4) VALUES('delete', %6);" + " INSERT INTO %2(rowid, %4) VALUES (%5);" + "END;"; + + const auto contentTableName = ftsTable.contentTable; + const auto ftsTableName = getFtsTableName(contentTableName); + const auto contentRowidName = ftsTable.contentRowid; + const auto contentColumnNames = ftsTable.columns.join(','); + const auto newTriggerColumnNames = + makeTriggerColumnNames(contentRowidName, ftsTable.columns, "new."); + const auto oldTriggerColumnNames = + makeTriggerColumnNames(contentRowidName, ftsTable.columns, "old."); + + const auto sql = + QString(ftsCreateSql) + .arg(contentTableName, ftsTableName, contentRowidName, contentColumnNames, + newTriggerColumnNames, oldTriggerColumnNames); + + return executeStr(sql); +} + bool SqliteDb::clearWithBackup(const char *sqlPragmas) { const QString oldEncoding = this->encoding(); diff --git a/src/ui/3rdparty/sqlite/sqlitedb.h b/src/ui/3rdparty/sqlite/sqlitedb.h index 66407655..8332350b 100644 --- a/src/ui/3rdparty/sqlite/sqlitedb.h +++ b/src/ui/3rdparty/sqlite/sqlitedb.h @@ -33,6 +33,13 @@ public: OpenDefaultReadWrite = (OpenReadWrite | OpenCreate | OpenNoMutex) }; + struct FtsTable + { + const QString contentTable; + const QString contentRowid; + const QStringList columns; + }; + struct MigrateOptions { const QString sqlDir; @@ -43,6 +50,7 @@ public: bool autoCopyTables = true; SQLITEDB_MIGRATE_FUNC migrateFunc = nullptr; void *migrateContext = nullptr; + QVector ftsTables; }; explicit SqliteDb( @@ -99,6 +107,8 @@ public: bool setBusyTimeoutMs(int v); + static QString getFtsTableName(const QString &tableName); + static QString migrationOldSchemaName(); static QString migrationNewSchemaName(); static QString entityName(const QString &schemaName, const QString &objectName); @@ -114,6 +124,9 @@ private: bool migrateDb(const MigrateOptions &opt, int userVersion, bool isNewDb); bool migrateSqlScripts(const MigrateOptions &opt, int userVersion, bool isNewDb); + bool createFtsTables(const MigrateOptions &opt); + bool createFtsTable(const FtsTable &ftsTable); + bool clearWithBackup(const char *sqlPragmas); bool importBackup(const MigrateOptions &opt); diff --git a/src/ui/appinfo/appinfomanager.cpp b/src/ui/appinfo/appinfomanager.cpp index 20b594ed..42450c7d 100644 --- a/src/ui/appinfo/appinfomanager.cpp +++ b/src/ui/appinfo/appinfomanager.cpp @@ -71,10 +71,12 @@ void AppInfoManager::setUp() return; } - SqliteDb::MigrateOptions opt = { .sqlDir = ":/appinfo/migrations", + SqliteDb::MigrateOptions opt = { + .sqlDir = ":/appinfo/migrations", .version = DATABASE_USER_VERSION, .recreate = true, - .importOldData = false }; + .importOldData = false, + }; if (!sqliteDb()->migrate(opt)) { qCCritical(LC) << "Migration error" << sqliteDb()->filePath(); diff --git a/src/ui/conf/confmanager.cpp b/src/ui/conf/confmanager.cpp index 88c8f835..59389edf 100644 --- a/src/ui/conf/confmanager.cpp +++ b/src/ui/conf/confmanager.cpp @@ -37,7 +37,7 @@ namespace { const QLoggingCategory LC("conf"); -constexpr int DATABASE_USER_VERSION = 21; +constexpr int DATABASE_USER_VERSION = 22; constexpr int APP_END_TIMER_INTERVAL_MIN = 100; constexpr int APP_END_TIMER_INTERVAL_MAX = 24 * 60 * 60 * 1000; // 1 day @@ -536,10 +536,19 @@ void ConfManager::setUp() return; } - SqliteDb::MigrateOptions opt = { .sqlDir = ":/conf/migrations", + SqliteDb::MigrateOptions opt = { + .sqlDir = ":/conf/migrations", .version = DATABASE_USER_VERSION, .recreate = true, - .migrateFunc = &migrateFunc }; + .migrateFunc = &migrateFunc, + .ftsTables = { + { + .contentTable = "app", + .contentRowid = "app_id", + .columns = { "path", "name" } + }, + }, + }; if (!sqliteDb()->migrate(opt)) { qCCritical(LC) << "Migration error" << sqliteDb()->filePath(); diff --git a/src/ui/stat/statmanager.cpp b/src/ui/stat/statmanager.cpp index 5d932448..a81715b4 100644 --- a/src/ui/stat/statmanager.cpp +++ b/src/ui/stat/statmanager.cpp @@ -81,10 +81,12 @@ void StatManager::setUp() return; } - SqliteDb::MigrateOptions opt = { .sqlDir = ":/stat/migrations/traf", + SqliteDb::MigrateOptions opt = { + .sqlDir = ":/stat/migrations/traf", .version = DATABASE_USER_VERSION, .recreate = true, - .migrateFunc = &migrateFunc }; + .migrateFunc = &migrateFunc, + }; if (!sqliteDb()->migrate(opt)) { qCCritical(LC) << "Migration error" << sqliteDb()->filePath();