using System.Reflection; using Microsoft.Data.Sqlite; namespace ClaudeDo.Data; /// /// Applies the embedded schema.sql script. Safe to call on every start — the script uses /// IF NOT EXISTS / INSERT OR IGNORE. /// public static class SchemaInitializer { private const string ResourceName = "ClaudeDo.Data.schema.sql"; public static void Apply(SqliteConnectionFactory factory) { using var conn = factory.Open(); ApplyTo(conn); } public static void ApplyTo(SqliteConnection conn) { var sql = LoadScript(); using var tx = conn.BeginTransaction(); using var cmd = conn.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = sql; cmd.ExecuteNonQuery(); tx.Commit(); ApplyMigrations(conn); } private static void ApplyMigrations(SqliteConnection conn) { string[] alterStatements = [ "ALTER TABLE tasks ADD COLUMN model TEXT NULL", "ALTER TABLE tasks ADD COLUMN system_prompt TEXT NULL", "ALTER TABLE tasks ADD COLUMN agent_path TEXT NULL", ]; foreach (var sql in alterStatements) { try { using var cmd = conn.CreateCommand(); cmd.CommandText = sql; cmd.ExecuteNonQuery(); } catch (SqliteException ex) when (ex.SqliteErrorCode == 1) { // Column already exists — safe to ignore. } } } private static string LoadScript() { var asm = typeof(SchemaInitializer).Assembly; using var stream = asm.GetManifestResourceStream(ResourceName) ?? throw new InvalidOperationException( $"Embedded resource '{ResourceName}' not found in {asm.GetName().Name}. " + $"Available: {string.Join(", ", asm.GetManifestResourceNames())}"); using var reader = new StreamReader(stream); return reader.ReadToEnd(); } }