UI: ConfManager: Add "app_fts" table for full-text search

This commit is contained in:
Nodir Temirkhodjaev 2023-08-14 13:13:33 +05:00
parent 787718a07e
commit db3a2b4aa4
6 changed files with 127 additions and 18 deletions

View File

@ -31,12 +31,12 @@
#define HAVE_MALLOC_USABLE_SIZE 1 #define HAVE_MALLOC_USABLE_SIZE 1
#define HAVE_ISNAN 1 #define HAVE_ISNAN 1
//#define SQLITE_ENABLE_FTS5 1 #define SQLITE_ENABLE_FTS5 1
//#define SQLITE_ENABLE_JSON1 1 //#define SQLITE_ENABLE_JSON1 1
#define SQLITE_ENABLE_MEMORY_MANAGEMENT 1 #define SQLITE_ENABLE_MEMORY_MANAGEMENT 1
//#define SQLITE_ENABLE_NULL_TRIM 1 //#define SQLITE_ENABLE_NULL_TRIM 1
//#define SQLITE_ENABLE_PREUPDATE_HOOK 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_STAT4 1
//#define SQLITE_ENABLE_UNLOCK_NOTIFY 1 //#define SQLITE_ENABLE_UNLOCK_NOTIFY 1
@ -62,7 +62,7 @@
#define SQLITE_OMIT_AUTOINIT 1 #define SQLITE_OMIT_AUTOINIT 1
#define SQLITE_OMIT_AUTOMATIC_INDEX 1 #define SQLITE_OMIT_AUTOMATIC_INDEX 1
#define SQLITE_OMIT_AUTORESET 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_CAST 1
#define SQLITE_OMIT_CHECK 1 #define SQLITE_OMIT_CHECK 1
#define SQLITE_OMIT_COMPLETE 1 #define SQLITE_OMIT_COMPLETE 1
@ -71,7 +71,7 @@
#define SQLITE_OMIT_EXPLAIN 1 #define SQLITE_OMIT_EXPLAIN 1
#define SQLITE_OMIT_FOREIGN_KEY 1 #define SQLITE_OMIT_FOREIGN_KEY 1
#define SQLITE_OMIT_GET_TABLE 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_INTEGRITY_CHECK 1
#define SQLITE_OMIT_JSON 1 #define SQLITE_OMIT_JSON 1
#define SQLITE_OMIT_LOAD_EXTENSION 1 #define SQLITE_OMIT_LOAD_EXTENSION 1

View File

@ -19,6 +19,8 @@ const char *const defaultSqlPragmas = "PRAGMA journal_mode = WAL;"
"PRAGMA synchronous = NORMAL;" "PRAGMA synchronous = NORMAL;"
"PRAGMA encoding = 'UTF-8';"; "PRAGMA encoding = 'UTF-8';";
const QString ftsTableSuffix = "_fts";
QAtomicInt g_sqliteInitCount; QAtomicInt g_sqliteInitCount;
bool removeDbFile(const QString &filePath) bool removeDbFile(const QString &filePath)
@ -45,6 +47,12 @@ bool renameDbFile(const QString &filePath, const QString &newFilePath)
return true; 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) : 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; return sqlite3_busy_timeout(m_db, v) == SQLITE_OK;
} }
QString SqliteDb::getFtsTableName(const QString &tableName)
{
return tableName + ftsTableSuffix;
}
QString SqliteDb::migrationOldSchemaName() QString SqliteDb::migrationOldSchemaName()
{ {
return QLatin1String("old"); return QLatin1String("old");
@ -269,8 +282,10 @@ QStringList SqliteDb::tableNames(const QString &schemaName)
const auto masterTable = entityName(schemaName, "sqlite_master"); const auto masterTable = entityName(schemaName, "sqlite_master");
const auto sql = QString("SELECT name FROM %1" const auto sql = QString("SELECT name FROM %1"
" WHERE type = 'table' AND name NOT LIKE 'sqlite_%';") " WHERE type = 'table'"
.arg(masterTable); " AND name NOT LIKE 'sqlite_%'"
" AND name NOT LIKE '%%2_%';")
.arg(masterTable, ftsTableSuffix);
SqliteStmt stmt; SqliteStmt stmt;
if (stmt.prepare(db(), sql.toLatin1())) { if (stmt.prepare(db(), sql.toLatin1())) {
@ -353,14 +368,14 @@ bool SqliteDb::migrateDb(const MigrateOptions &opt, int userVersion, bool isNewD
} }
// Run migration SQL scripts // Run migration SQL scripts
bool success = migrateSqlScripts(opt, userVersion, isNewDb); if (!migrateSqlScripts(opt, userVersion, isNewDb))
return false;
// Re-create the DB: End // Re-create the DB: End
if (success && opt.recreate) { if (opt.recreate && !(createFtsTables(opt) && importBackup(opt)))
success = importBackup(opt); return false;
}
return success; return true;
} }
bool SqliteDb::migrateSqlScripts(const MigrateOptions &opt, int userVersion, bool isNewDb) 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; 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) bool SqliteDb::clearWithBackup(const char *sqlPragmas)
{ {
const QString oldEncoding = this->encoding(); const QString oldEncoding = this->encoding();

View File

@ -33,6 +33,13 @@ public:
OpenDefaultReadWrite = (OpenReadWrite | OpenCreate | OpenNoMutex) OpenDefaultReadWrite = (OpenReadWrite | OpenCreate | OpenNoMutex)
}; };
struct FtsTable
{
const QString contentTable;
const QString contentRowid;
const QStringList columns;
};
struct MigrateOptions struct MigrateOptions
{ {
const QString sqlDir; const QString sqlDir;
@ -43,6 +50,7 @@ public:
bool autoCopyTables = true; bool autoCopyTables = true;
SQLITEDB_MIGRATE_FUNC migrateFunc = nullptr; SQLITEDB_MIGRATE_FUNC migrateFunc = nullptr;
void *migrateContext = nullptr; void *migrateContext = nullptr;
QVector<FtsTable> ftsTables;
}; };
explicit SqliteDb( explicit SqliteDb(
@ -99,6 +107,8 @@ public:
bool setBusyTimeoutMs(int v); bool setBusyTimeoutMs(int v);
static QString getFtsTableName(const QString &tableName);
static QString migrationOldSchemaName(); static QString migrationOldSchemaName();
static QString migrationNewSchemaName(); static QString migrationNewSchemaName();
static QString entityName(const QString &schemaName, const QString &objectName); static QString entityName(const QString &schemaName, const QString &objectName);
@ -114,6 +124,9 @@ private:
bool migrateDb(const MigrateOptions &opt, int userVersion, bool isNewDb); bool migrateDb(const MigrateOptions &opt, int userVersion, bool isNewDb);
bool migrateSqlScripts(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 clearWithBackup(const char *sqlPragmas);
bool importBackup(const MigrateOptions &opt); bool importBackup(const MigrateOptions &opt);

View File

@ -71,10 +71,12 @@ void AppInfoManager::setUp()
return; return;
} }
SqliteDb::MigrateOptions opt = { .sqlDir = ":/appinfo/migrations", SqliteDb::MigrateOptions opt = {
.sqlDir = ":/appinfo/migrations",
.version = DATABASE_USER_VERSION, .version = DATABASE_USER_VERSION,
.recreate = true, .recreate = true,
.importOldData = false }; .importOldData = false,
};
if (!sqliteDb()->migrate(opt)) { if (!sqliteDb()->migrate(opt)) {
qCCritical(LC) << "Migration error" << sqliteDb()->filePath(); qCCritical(LC) << "Migration error" << sqliteDb()->filePath();

View File

@ -37,7 +37,7 @@ namespace {
const QLoggingCategory LC("conf"); 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_MIN = 100;
constexpr int APP_END_TIMER_INTERVAL_MAX = 24 * 60 * 60 * 1000; // 1 day constexpr int APP_END_TIMER_INTERVAL_MAX = 24 * 60 * 60 * 1000; // 1 day
@ -536,10 +536,19 @@ void ConfManager::setUp()
return; return;
} }
SqliteDb::MigrateOptions opt = { .sqlDir = ":/conf/migrations", SqliteDb::MigrateOptions opt = {
.sqlDir = ":/conf/migrations",
.version = DATABASE_USER_VERSION, .version = DATABASE_USER_VERSION,
.recreate = true, .recreate = true,
.migrateFunc = &migrateFunc }; .migrateFunc = &migrateFunc,
.ftsTables = {
{
.contentTable = "app",
.contentRowid = "app_id",
.columns = { "path", "name" }
},
},
};
if (!sqliteDb()->migrate(opt)) { if (!sqliteDb()->migrate(opt)) {
qCCritical(LC) << "Migration error" << sqliteDb()->filePath(); qCCritical(LC) << "Migration error" << sqliteDb()->filePath();

View File

@ -81,10 +81,12 @@ void StatManager::setUp()
return; return;
} }
SqliteDb::MigrateOptions opt = { .sqlDir = ":/stat/migrations/traf", SqliteDb::MigrateOptions opt = {
.sqlDir = ":/stat/migrations/traf",
.version = DATABASE_USER_VERSION, .version = DATABASE_USER_VERSION,
.recreate = true, .recreate = true,
.migrateFunc = &migrateFunc }; .migrateFunc = &migrateFunc,
};
if (!sqliteDb()->migrate(opt)) { if (!sqliteDb()->migrate(opt)) {
qCCritical(LC) << "Migration error" << sqliteDb()->filePath(); qCCritical(LC) << "Migration error" << sqliteDb()->filePath();