using Microsoft.Data.Sqlite; using System; namespace Elwig.Helpers { public static class AppDbUpdater { public static readonly int RequiredSchemaVersion = 1; private static int _versionOffset = 0; private static readonly Action[] _updaters = new[] { UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3 }; private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { using var cmd = cnx.CreateCommand(); cmd.CommandText = sql; cmd.ExecuteNonQuery(); } private static object? ExecuteScalar(SqliteConnection cnx, string sql) { using var cmd = cnx.CreateCommand(); cmd.CommandText = sql; return cmd.ExecuteScalar(); } public static string CheckDb() { using var cnx = AppDbContext.Connect(); var applId = (long?)ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; if (applId != 0x454C5747) throw new Exception("Invalid application_id of database"); var schemaVers = (long?)ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; _versionOffset = (int)(schemaVers % 100); if (_versionOffset != 0) { // schema was modified manually/externally // TODO issue warning } UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion); var userVers = (long?)ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; var major = userVers >> 24; var minor = (userVers >> 16) & 0xFF; var patch = userVers & 0xFFFF; if (App.VersionMajor > major || App.VersionMinor > minor || App.VersionPatch > patch) { long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch; ExecuteNonQuery(cnx, $"PRAGMA user_version = {vers}"); } return $"{major}.{minor}.{patch}"; } private static void UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) { if (fromVersion == toVersion) { return; } else if (fromVersion > toVersion) { throw new Exception("schema_version of database is too new"); } else if (toVersion - 1 > _updaters.Length) { throw new Exception("Unable to update database schema: Updater not implemented"); } else if (fromVersion <= 0) { throw new Exception("schema_version of database is invalid"); } ExecuteNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE"); ExecuteNonQuery(cnx, "BEGIN EXCLUSIVE"); for (int i = fromVersion; i < toVersion; i++) { _updaters[i - 1](cnx); } ExecuteNonQuery(cnx, "COMMIT"); ExecuteNonQuery(cnx, "VACUUM"); ExecuteNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}"); } private static void UpdateDbSchema_1_To_2(SqliteConnection cnx) { } private static void UpdateDbSchema_2_To_3(SqliteConnection cnx) { } } }