This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,31 +14,36 @@ namespace Elwig.Helpers {
|
|||||||
private static int VersionOffset = 0;
|
private static int VersionOffset = 0;
|
||||||
|
|
||||||
public static async Task<Version> CheckDb() {
|
public static async Task<Version> CheckDb() {
|
||||||
using var cnx = AppDbContext.Connect();
|
long? applId, schemaVers;
|
||||||
|
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||||
|
applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||||
|
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
||||||
|
|
||||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
||||||
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
VersionOffset = (int)(schemaVers % 100);
|
||||||
|
if (VersionOffset != 0) {
|
||||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
// schema was modified manually/externally
|
||||||
VersionOffset = (int)(schemaVers % 100);
|
// TODO issue warning
|
||||||
if (VersionOffset != 0) {
|
}
|
||||||
// schema was modified manually/externally
|
|
||||||
// TODO issue warning
|
|
||||||
}
|
}
|
||||||
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
|
|
||||||
|
|
||||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
await UpdateDbSchema((int)(schemaVers / 100), RequiredSchemaVersion);
|
||||||
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
|
|
||||||
|
|
||||||
if (App.Version > v) {
|
Version v;
|
||||||
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
|
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||||
await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
|
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||||
|
v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
|
||||||
|
|
||||||
|
if (App.Version > v) {
|
||||||
|
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
|
||||||
|
await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {
|
private static async Task UpdateDbSchema(int fromVersion, int toVersion) {
|
||||||
if (fromVersion == toVersion) {
|
if (fromVersion == toVersion) {
|
||||||
return;
|
return;
|
||||||
} else if (fromVersion > toVersion) {
|
} else if (fromVersion > toVersion) {
|
||||||
@@ -48,43 +53,48 @@ namespace Elwig.Helpers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var asm = Assembly.GetExecutingAssembly();
|
var asm = Assembly.GetExecutingAssembly();
|
||||||
(int From, int To, string Name)[] scripts = asm.GetManifestResourceNames()
|
(int From, int To, string Name)[] scripts = [.. asm.GetManifestResourceNames()
|
||||||
.Where(n => n.StartsWith("Elwig.Resources.Sql."))
|
.Where(n => n.StartsWith("Elwig.Resources.Sql."))
|
||||||
.Select(n => {
|
.Select(n => {
|
||||||
var p = n.Split(".")[^2].Split("-");
|
var p = n.Split(".")[^2].Split("-");
|
||||||
return (int.Parse(p[0]), int.Parse(p[1]), n);
|
return (int.Parse(p[0]), int.Parse(p[1]), n);
|
||||||
})
|
})
|
||||||
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)
|
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)];
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
List<string> toExecute = [];
|
List<string> toExecute = [];
|
||||||
var vers = fromVersion;
|
var vers = fromVersion;
|
||||||
while (vers < toVersion) {
|
while (vers < toVersion) {
|
||||||
var (_, to, name) = scripts.Where(s => s.From == vers).Last();
|
var (_, to, name) = scripts.Last(s => s.From == vers);
|
||||||
toExecute.Add(name);
|
toExecute.Add(name);
|
||||||
vers = to;
|
vers = to;
|
||||||
}
|
}
|
||||||
if (toExecute.Count == 0)
|
if (toExecute.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await cnx.ExecuteBatch("""
|
var backup = Path.ChangeExtension(App.Config.DatabaseFile, $".v{fromVersion}.sqlite3");
|
||||||
PRAGMA locking_mode = EXCLUSIVE;
|
File.Copy(App.Config.DatabaseFile, backup, true);
|
||||||
BEGIN EXCLUSIVE;
|
try {
|
||||||
""");
|
using var cnx = await AppDbContext.ConnectAsync();
|
||||||
foreach (var script in toExecute) {
|
await cnx.ExecuteBatch("PRAGMA locking_mode = EXCLUSIVE");
|
||||||
await cnx.ExecuteEmbeddedScript(asm, script);
|
foreach (var script in toExecute) {
|
||||||
}
|
await cnx.ExecuteEmbeddedScript(asm, script);
|
||||||
var violations = await cnx.ForeignKeyCheck();
|
}
|
||||||
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 cnx.ExecuteBatch($"""
|
var violations = await cnx.ForeignKeyCheck();
|
||||||
COMMIT;
|
if (violations.Length > 0) {
|
||||||
VACUUM;
|
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
|
||||||
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
|
.Take(50)
|
||||||
""");
|
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
await cnx.ExecuteBatch("VACUUM");
|
||||||
|
await cnx.ExecuteBatch($"PRAGMA schema_version = {toVersion * 100 + VersionOffset}");
|
||||||
|
} catch (Exception) {
|
||||||
|
File.Move(backup, App.Config.DatabaseFile, true);
|
||||||
|
throw;
|
||||||
|
} finally {
|
||||||
|
File.Delete(backup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user