Bind startup migrations to module schema search path
This commit is contained in:
@@ -91,6 +91,7 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
{
|
||||
// Step 2: Ensure schema and migrations table exist
|
||||
await EnsureSchemaAsync(connection, cancellationToken).ConfigureAwait(false);
|
||||
await SetSearchPathAsync(connection, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureMigrationsTableAsync(connection, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Step 3: Load and categorize migrations
|
||||
@@ -237,17 +238,28 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
|
||||
private async Task EnsureSchemaAsync(NpgsqlConnection connection, CancellationToken cancellationToken)
|
||||
{
|
||||
var quotedSchema = QuoteIdentifier(_schemaName);
|
||||
await using var command = new NpgsqlCommand(
|
||||
$"CREATE SCHEMA IF NOT EXISTS {_schemaName}",
|
||||
$"CREATE SCHEMA IF NOT EXISTS {quotedSchema}",
|
||||
connection);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SetSearchPathAsync(NpgsqlConnection connection, CancellationToken cancellationToken)
|
||||
{
|
||||
var quotedSchema = QuoteIdentifier(_schemaName);
|
||||
await using var command = new NpgsqlCommand(
|
||||
$"SET search_path TO {quotedSchema}, public",
|
||||
connection);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task EnsureMigrationsTableAsync(NpgsqlConnection connection, CancellationToken cancellationToken)
|
||||
{
|
||||
var quotedSchema = QuoteIdentifier(_schemaName);
|
||||
await using var command = new NpgsqlCommand(
|
||||
$"""
|
||||
CREATE TABLE IF NOT EXISTS {_schemaName}.schema_migrations (
|
||||
CREATE TABLE IF NOT EXISTS {quotedSchema}.schema_migrations (
|
||||
migration_name TEXT PRIMARY KEY,
|
||||
category TEXT NOT NULL DEFAULT 'startup',
|
||||
checksum TEXT NOT NULL,
|
||||
@@ -258,7 +270,7 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_schema_migrations_applied_at
|
||||
ON {_schemaName}.schema_migrations(applied_at DESC);
|
||||
ON {quotedSchema}.schema_migrations(applied_at DESC);
|
||||
""",
|
||||
connection);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -269,9 +281,10 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new Dictionary<string, AppliedMigration>(StringComparer.Ordinal);
|
||||
var quotedSchema = QuoteIdentifier(_schemaName);
|
||||
|
||||
await using var command = new NpgsqlCommand(
|
||||
$"SELECT migration_name, category, checksum, applied_at FROM {_schemaName}.schema_migrations",
|
||||
$"SELECT migration_name, category, checksum, applied_at FROM {quotedSchema}.schema_migrations",
|
||||
connection);
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -350,6 +363,7 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
migration.Name, migration.Category);
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var quotedSchema = QuoteIdentifier(_schemaName);
|
||||
|
||||
await using var transaction = await connection.BeginTransactionAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -366,7 +380,7 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
// Record migration
|
||||
await using (var recordCommand = new NpgsqlCommand(
|
||||
$"""
|
||||
INSERT INTO {_schemaName}.schema_migrations
|
||||
INSERT INTO {quotedSchema}.schema_migrations
|
||||
(migration_name, category, checksum, duration_ms, applied_by)
|
||||
VALUES (@name, @category, @checksum, @duration, @applied_by)
|
||||
ON CONFLICT (migration_name) DO NOTHING
|
||||
@@ -434,6 +448,12 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
return lastSlash >= 0 ? resourceName[(lastSlash + 1)..] : resourceName;
|
||||
}
|
||||
|
||||
private static string QuoteIdentifier(string identifier)
|
||||
{
|
||||
var escaped = identifier.Replace("\"", "\"\"", StringComparison.Ordinal);
|
||||
return $"\"{escaped}\"";
|
||||
}
|
||||
|
||||
private record AppliedMigration(string Name, string Category, string Checksum, DateTimeOffset AppliedAt);
|
||||
private record PendingMigration(string Name, string ResourceName, MigrationCategory Category, string Checksum, string Content);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user