95 lines
3.8 KiB
C#
95 lines
3.8 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Elwig.Helpers {
|
|
public static class AppDbUpdater {
|
|
|
|
// Don't forget to update value in Tests/fetch-resources.bat!
|
|
public static readonly int RequiredSchemaVersion = 24;
|
|
|
|
private static int VersionOffset = 0;
|
|
|
|
public static async Task<string> CheckDb() {
|
|
using var cnx = AppDbContext.Connect();
|
|
|
|
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
|
|
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
|
|
|
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
|
|
VersionOffset = (int)(schemaVers % 100);
|
|
if (VersionOffset != 0) {
|
|
// schema was modified manually/externally
|
|
// TODO issue warning
|
|
}
|
|
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
|
|
|
|
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
|
|
var major = userVers >> 24;
|
|
var minor = (userVers >> 16) & 0xFF;
|
|
var patch = userVers & 0xFFFF;
|
|
|
|
if (App.VersionMajor > major ||
|
|
(App.VersionMajor == major && App.VersionMinor > minor) ||
|
|
(App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) {
|
|
long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch;
|
|
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}");
|
|
}
|
|
|
|
return $"{major}.{minor}.{patch}";
|
|
}
|
|
|
|
private static async Task 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 (fromVersion <= 0) {
|
|
throw new Exception("schema_version of database is invalid");
|
|
}
|
|
|
|
var asm = Assembly.GetExecutingAssembly();
|
|
(int From, int To, string Name)[] scripts = asm.GetManifestResourceNames()
|
|
.Where(n => n.StartsWith("Elwig.Resources.Sql."))
|
|
.Select(n => {
|
|
var p = n.Split(".")[^2].Split("-");
|
|
return (int.Parse(p[0]), int.Parse(p[1]), n);
|
|
})
|
|
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)
|
|
.ToArray();
|
|
|
|
List<string> toExecute = [];
|
|
var vers = fromVersion;
|
|
while (vers < toVersion) {
|
|
var (_, to, name) = scripts.Where(s => s.From == vers).Last();
|
|
toExecute.Add(name);
|
|
vers = to;
|
|
}
|
|
if (toExecute.Count == 0)
|
|
return;
|
|
|
|
await AppDbContext.ExecuteBatch(cnx, """
|
|
PRAGMA locking_mode = EXCLUSIVE;
|
|
BEGIN EXCLUSIVE;
|
|
""");
|
|
foreach (var script in toExecute) {
|
|
await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script);
|
|
}
|
|
var violations = await AppDbContext.ForeignKeyCheck(cnx);
|
|
if (violations.Length > 0) {
|
|
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
|
|
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
|
}
|
|
|
|
await AppDbContext.ExecuteBatch(cnx, $"""
|
|
COMMIT;
|
|
VACUUM;
|
|
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
|
|
""");
|
|
}
|
|
}
|
|
}
|