Files
elwig/Elwig/Helpers/AppDbUpdater.cs
Lorenz Stechauner d8a10152b3
All checks were successful
Test / Run tests (push) Successful in 1m37s
DeliveryScheduleAdminWindow: Add Attribute, Cultivation and IsCancelled
2024-09-16 10:10:31 +02:00

91 lines
3.6 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 = 29;
private static int VersionOffset = 0;
public static async Task<Version> 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 v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)(userVers & 0xFFFF));
if (App.Version > v) {
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | App.Version.Build;
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}");
}
return v;
}
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};
""");
}
}
}