mirror of
https://github.com/tnodir/fort
synced 2024-11-15 08:25:20 +00:00
UI: Migrate config by re-creating the DB.
This commit is contained in:
parent
3d4d59ae64
commit
f05b94d3b2
211
src/ui/3rdparty/sqlite/sqlitedb.cpp
vendored
211
src/ui/3rdparty/sqlite/sqlitedb.cpp
vendored
@ -1,13 +1,46 @@
|
||||
#include "sqlitedb.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QLoggingCategory>
|
||||
#include <QSet>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "sqlitestmt.h"
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(CLOG_SQLITEDB)
|
||||
Q_LOGGING_CATEGORY(CLOG_SQLITEDB, "fort.sqlitedb")
|
||||
|
||||
#define dbWarning() qCWarning(CLOG_SQLITEDB,)
|
||||
#define dbCritical() qCCritical(CLOG_SQLITEDB,)
|
||||
|
||||
namespace {
|
||||
|
||||
bool removeDbFile(const QString &filePath)
|
||||
{
|
||||
if (!filePath.startsWith(QLatin1Char(':'))
|
||||
&& QFile::exists(filePath)
|
||||
&& !QFile::remove(filePath)) {
|
||||
dbCritical() << "Cannot remove file:" << filePath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool renameDbFile(const QString &filePath, const QString &newFilePath)
|
||||
{
|
||||
removeDbFile(newFilePath);
|
||||
|
||||
if (!QFile::rename(filePath, newFilePath)) {
|
||||
dbCritical() << "Cannot rename file" << filePath << "to" << newFilePath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SqliteDb::SqliteDb(const QString &filePath) :
|
||||
m_db(nullptr),
|
||||
m_filePath(filePath)
|
||||
@ -39,11 +72,18 @@ void SqliteDb::close()
|
||||
}
|
||||
}
|
||||
|
||||
bool SqliteDb::recreateDb()
|
||||
bool SqliteDb::attach(const QString &schemaName, const QString &filePath)
|
||||
{
|
||||
close();
|
||||
QFile::remove(m_filePath);
|
||||
return open(m_filePath);
|
||||
bool ok = false;
|
||||
executeEx("ATTACH DATABASE ?1 AS ?2;", {filePath, schemaName}, 0, &ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SqliteDb::detach(const QString &schemaName)
|
||||
{
|
||||
bool ok = false;
|
||||
executeEx("DETACH DATABASE ?1;", {schemaName}, 0, &ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SqliteDb::execute(const char *sql)
|
||||
@ -75,21 +115,21 @@ QVariant SqliteDb::executeEx(const char *sql,
|
||||
QVariantList list;
|
||||
|
||||
SqliteStmt stmt;
|
||||
bool res = true;
|
||||
bool success = true;
|
||||
|
||||
if (stmt.prepare(db(), sql)) {
|
||||
// Bind variables
|
||||
if (!vars.isEmpty()) {
|
||||
int index = 0;
|
||||
for (const QVariant &v : vars) {
|
||||
res = stmt.bindVar(++index, v);
|
||||
if (!res) break;
|
||||
success = stmt.bindVar(++index, v);
|
||||
if (!success) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
if (success) {
|
||||
const auto stepRes = stmt.step();
|
||||
res = (stepRes != SqliteStmt::StepError);
|
||||
success = (stepRes != SqliteStmt::StepError);
|
||||
|
||||
// Get result
|
||||
if (stepRes == SqliteStmt::StepRow) {
|
||||
@ -102,7 +142,7 @@ QVariant SqliteDb::executeEx(const char *sql,
|
||||
}
|
||||
|
||||
if (ok != nullptr) {
|
||||
*ok = res;
|
||||
*ok = success;
|
||||
}
|
||||
|
||||
const int listSize = list.size();
|
||||
@ -174,8 +214,56 @@ int SqliteDb::userVersion()
|
||||
return executeEx("PRAGMA user_version;").toInt();
|
||||
}
|
||||
|
||||
QString SqliteDb::entityName(const QString &schemaName,
|
||||
const QString &objectName)
|
||||
{
|
||||
return schemaName.isEmpty() ? objectName
|
||||
: schemaName + '.' + objectName;
|
||||
}
|
||||
|
||||
QStringList SqliteDb::tableNames(const QString &schemaName)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
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);
|
||||
|
||||
SqliteStmt stmt;
|
||||
if (stmt.prepare(db(), sql.toLatin1())) {
|
||||
while (stmt.step() == SqliteStmt::StepRow) {
|
||||
list.append(stmt.columnText());
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList SqliteDb::columnNames(const QString &tableName,
|
||||
const QString &schemaName)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
const auto schemaTableName = entityName(schemaName, tableName);
|
||||
const auto sql = QString(
|
||||
"SELECT * FROM %1 WHERE 0 = 1;"
|
||||
).arg(schemaTableName);
|
||||
|
||||
SqliteStmt stmt;
|
||||
if (stmt.prepare(db(), sql.toLatin1())) {
|
||||
const int n = stmt.columnCount();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
list.append(stmt.columnName(i));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
bool recreate,
|
||||
bool recreate, bool importOldData,
|
||||
SQLITEDB_MIGRATE_FUNC migrateFunc,
|
||||
void *migrateContext)
|
||||
{
|
||||
@ -185,15 +273,22 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
return true;
|
||||
|
||||
if (userVersion > version) {
|
||||
qWarning() << "SQLite: Cannot open new DB" << userVersion
|
||||
<< "from old code" << version;
|
||||
dbWarning() << "Cannot open new DB" << userVersion
|
||||
<< "from old application" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Re-create the DB
|
||||
QString tempFilePath;
|
||||
if (recreate) {
|
||||
if (!recreateDb()) {
|
||||
qWarning() << "SQLite: Cannot re-create the DB" << m_filePath;
|
||||
close();
|
||||
|
||||
tempFilePath = m_filePath + ".temp";
|
||||
|
||||
if (!(renameDbFile(m_filePath, tempFilePath)
|
||||
&& open(m_filePath))) {
|
||||
dbWarning() << "Cannot re-create the DB" << m_filePath;
|
||||
renameDbFile(tempFilePath, m_filePath);
|
||||
return false;
|
||||
}
|
||||
userVersion = 0;
|
||||
@ -201,7 +296,7 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
|
||||
// Run migration SQL scripts
|
||||
QDir dir(sqlDir);
|
||||
bool res = true;
|
||||
bool success = true;
|
||||
|
||||
beginTransaction();
|
||||
|
||||
@ -213,16 +308,16 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
continue;
|
||||
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qWarning() << "SQLite: Cannot open migration file" << filePath
|
||||
<< file.errorString();
|
||||
res = false;
|
||||
dbWarning() << "Cannot open migration file" << filePath
|
||||
<< file.errorString();
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const QByteArray data = file.readAll();
|
||||
if (data.isEmpty()) {
|
||||
qWarning() << "SQLite: Migration file is empty" << filePath;
|
||||
res = false;
|
||||
dbWarning() << "Migration file is empty" << filePath;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -230,8 +325,8 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
if (!execute(data.constData())
|
||||
|| !(migrateFunc == nullptr
|
||||
|| migrateFunc(this, i, migrateContext))) {
|
||||
qWarning() << "SQLite: Migration error:" << filePath << errorMessage();
|
||||
res = false;
|
||||
dbCritical() << "Migration error:" << filePath << errorMessage();
|
||||
success = false;
|
||||
rollbackSavepoint();
|
||||
break;
|
||||
}
|
||||
@ -240,5 +335,71 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
|
||||
|
||||
commitTransaction();
|
||||
|
||||
return res;
|
||||
// Re-create the DB: End
|
||||
if (recreate) {
|
||||
// Re-import the DB
|
||||
if (success && importOldData) {
|
||||
success = importDb(tempFilePath);
|
||||
}
|
||||
|
||||
// Remove the old DB
|
||||
if (success) {
|
||||
removeDbFile(tempFilePath);
|
||||
} else {
|
||||
renameDbFile(tempFilePath, m_filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SqliteDb::importDb(const QString &sourceFilePath)
|
||||
{
|
||||
const QLatin1String srcSchema("src");
|
||||
const QLatin1String dstSchema("main");
|
||||
|
||||
if (!attach(srcSchema, sourceFilePath)) {
|
||||
dbWarning() << "Cannot attach the DB" << sourceFilePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Import Data
|
||||
bool success = true;
|
||||
|
||||
beginTransaction();
|
||||
|
||||
for (const auto &tableName : tableNames(srcSchema)) {
|
||||
const auto dstColumns = columnNames(tableName, dstSchema);
|
||||
if (dstColumns.isEmpty())
|
||||
continue; // new schema doesn't contain old table
|
||||
|
||||
const auto srcColumns = columnNames(tableName, srcSchema);
|
||||
if (srcColumns.isEmpty())
|
||||
continue; // empty old table
|
||||
|
||||
// Intersect column names
|
||||
auto columnsSet = QSet<QString>(srcColumns.constBegin(), srcColumns.constEnd());
|
||||
const auto dstColumnsSet = QSet<QString>(dstColumns.constBegin(), dstColumns.constEnd());
|
||||
columnsSet.intersect(dstColumnsSet);
|
||||
|
||||
const QStringList columns(columnsSet.constBegin(), columnsSet.constEnd());
|
||||
const QString columnNames = columns.join(", ");
|
||||
|
||||
// Insert
|
||||
const auto sql = QString("INSERT INTO %1 (%3) SELECT %3 FROM %2;")
|
||||
.arg(entityName(dstSchema, tableName),
|
||||
entityName(srcSchema, tableName),
|
||||
columnNames);
|
||||
|
||||
if (!execute(sql.toLatin1())) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
endTransaction(success);
|
||||
|
||||
detach(srcSchema);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
13
src/ui/3rdparty/sqlite/sqlitedb.h
vendored
13
src/ui/3rdparty/sqlite/sqlitedb.h
vendored
@ -25,7 +25,9 @@ public:
|
||||
bool open(const QString &filePath = QString());
|
||||
void close();
|
||||
|
||||
bool recreateDb();
|
||||
bool attach(const QString &schemaName,
|
||||
const QString &filePath = QString());
|
||||
bool detach(const QString &schemaName);
|
||||
|
||||
bool execute(const char *sql);
|
||||
bool execute16(const ushort *sql);
|
||||
@ -52,11 +54,20 @@ public:
|
||||
|
||||
int userVersion();
|
||||
|
||||
static QString entityName(const QString &schemaName,
|
||||
const QString &objectName);
|
||||
QStringList tableNames(const QString &schemaName = QString());
|
||||
QStringList columnNames(const QString &tableName,
|
||||
const QString &schemaName = QString());
|
||||
|
||||
bool migrate(const QString &sqlDir, int version,
|
||||
bool recreate = false,
|
||||
bool importOldData = false,
|
||||
SQLITEDB_MIGRATE_FUNC migrateFunc = nullptr,
|
||||
void *migrateContext = nullptr);
|
||||
|
||||
bool importDb(const QString &sourceFilePath);
|
||||
|
||||
private:
|
||||
sqlite3 *m_db;
|
||||
QString m_filePath;
|
||||
|
11
src/ui/3rdparty/sqlite/sqlitestmt.cpp
vendored
11
src/ui/3rdparty/sqlite/sqlitestmt.cpp
vendored
@ -172,6 +172,17 @@ int SqliteStmt::dataCount()
|
||||
return sqlite3_data_count(m_stmt);
|
||||
}
|
||||
|
||||
int SqliteStmt::columnCount()
|
||||
{
|
||||
return sqlite3_column_count(m_stmt);
|
||||
}
|
||||
|
||||
QString SqliteStmt::columnName(int column)
|
||||
{
|
||||
const auto name = sqlite3_column_name16(m_stmt, column);
|
||||
return QString::fromWCharArray((const wchar_t *) name);
|
||||
}
|
||||
|
||||
qint32 SqliteStmt::columnInt(int column)
|
||||
{
|
||||
return sqlite3_column_int(m_stmt, column);
|
||||
|
2
src/ui/3rdparty/sqlite/sqlitestmt.h
vendored
2
src/ui/3rdparty/sqlite/sqlitestmt.h
vendored
@ -48,7 +48,9 @@ public:
|
||||
StepResult step();
|
||||
|
||||
int dataCount();
|
||||
int columnCount();
|
||||
|
||||
QString columnName(int column = 0);
|
||||
qint32 columnInt(int column = 0);
|
||||
qint64 columnInt64(int column = 0);
|
||||
double columnDouble(int column = 0);
|
||||
|
@ -21,7 +21,7 @@ Q_LOGGING_CATEGORY(CLOG_CONF_MANAGER, "fort.confManager")
|
||||
#define logWarning() qCWarning(CLOG_CONF_MANAGER,)
|
||||
#define logCritical() qCCritical(CLOG_CONF_MANAGER,)
|
||||
|
||||
#define DATABASE_USER_VERSION 1
|
||||
#define DATABASE_USER_VERSION 2
|
||||
|
||||
namespace {
|
||||
|
||||
@ -150,7 +150,8 @@ bool ConfManager::initialize()
|
||||
|
||||
m_sqliteDb->execute(sqlPragmas);
|
||||
|
||||
if (!m_sqliteDb->migrate(":/conf/migrations", DATABASE_USER_VERSION)) {
|
||||
if (!m_sqliteDb->migrate(":/conf/migrations",
|
||||
DATABASE_USER_VERSION, true, true)) {
|
||||
logCritical() << "Migration error"
|
||||
<< m_sqliteDb->filePath();
|
||||
return false;
|
||||
|
@ -1,4 +1,4 @@
|
||||
PRAGMA user_version = 1;
|
||||
PRAGMA user_version = 2;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS address_group(
|
||||
addr_group_id INTEGER PRIMARY KEY,
|
||||
@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS app_group(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app(
|
||||
app_id INTEGER PRIMARY KEY,
|
||||
app_group_id INTEGER NOT NULL,
|
||||
app_group_id INTEGER NOT NULL DEFAULT 0,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
blocked BOOLEAN NOT NULL,
|
||||
creat_time INTEGER NOT NULL,
|
||||
|
@ -109,7 +109,7 @@ bool StatManager::initialize()
|
||||
m_sqliteDb->execute(StatSql::sqlPragmas);
|
||||
|
||||
if (!m_sqliteDb->migrate(":/stat/migrations", DATABASE_USER_VERSION,
|
||||
false, &migrateFunc)) {
|
||||
false, false, &migrateFunc)) {
|
||||
logCritical() << "Migration error"
|
||||
<< m_sqliteDb->filePath();
|
||||
return false;
|
||||
|
Loading…
Reference in New Issue
Block a user