-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSchemaMigrator.cs
More file actions
98 lines (83 loc) · 3.58 KB
/
Copy pathSchemaMigrator.cs
File metadata and controls
98 lines (83 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// -----------------------------------------------------------------------
// <copyright file="SchemaMigrator.cs" company="Petabridge, LLC">
// Copyright (C) 2026 - 2026 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------
using Microsoft.Data.Sqlite;
namespace SkillServer.Data;
public static class SchemaMigrator
{
public static async Task MigrateAsync(string connectionString, ILogger logger,
CancellationToken ct = default)
{
logger.LogInformation("Starting database schema migration...");
await using var conn = new SqliteConnection(connectionString);
await conn.OpenAsync(ct);
await using (var cmd = conn.CreateCommand())
{
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS schema_version (
version INT PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL
);
""";
await cmd.ExecuteNonQueryAsync(ct);
}
var appliedMigrations = new HashSet<int>();
await using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT version FROM schema_version";
await using var reader = await cmd.ExecuteReaderAsync(ct);
while (await reader.ReadAsync(ct))
{
appliedMigrations.Add(reader.GetInt32(0));
}
}
var migrationsDir = Path.Combine(AppContext.BaseDirectory, "migrations");
if (!Directory.Exists(migrationsDir))
{
logger.LogWarning("Migrations directory not found: {MigrationsDir}", migrationsDir);
return;
}
var migrationFiles = Directory.GetFiles(migrationsDir, "*.sql")
.Select(f => new { Path = f, Name = Path.GetFileName(f) })
.Select(f => new { f.Path, f.Name, Version = ParseVersion(f.Name) })
.Where(f => f.Version != null)
.OrderBy(f => f.Version)
.ToList();
foreach (var migration in migrationFiles)
{
var version = migration.Version!.Value;
if (appliedMigrations.Contains(version))
{
logger.LogInformation("Migration {Name} already applied", migration.Name);
continue;
}
logger.LogInformation("Applying migration: {Name}", migration.Name);
var sql = await File.ReadAllTextAsync(migration.Path, ct);
await using (var cmd = conn.CreateCommand())
{
cmd.CommandText = sql;
await cmd.ExecuteNonQueryAsync(ct);
}
await using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO schema_version (version, name, applied_at) VALUES (@version, @name, @appliedAt)";
cmd.Parameters.AddWithValue("@version", version);
cmd.Parameters.AddWithValue("@name", migration.Name);
cmd.Parameters.AddWithValue("@appliedAt", DateTimeOffset.UtcNow.ToString("o"));
await cmd.ExecuteNonQueryAsync(ct);
}
logger.LogInformation("Migration {Name} applied successfully", migration.Name);
}
logger.LogInformation("Database schema migration completed successfully");
}
internal static int? ParseVersion(string fileName)
{
var parts = fileName.Split('_', 2);
if (parts.Length < 2) return null;
if (int.TryParse(parts[0], out var version)) return version;
return null;
}
}