using Microsoft.EntityFrameworkCore; namespace MeetingAssistant.Speakers; internal static class SpeakerIdentitySchema { public static async Task EnsureCreatedOrUpdatedAsync( SpeakerIdentityDbContext context, CancellationToken cancellationToken) { await context.Database.EnsureCreatedAsync(cancellationToken); await EnsureSpeakerIdentityTimestampColumnsAsync(context, cancellationToken); await context.Database.ExecuteSqlRawAsync( """ CREATE TABLE IF NOT EXISTS "SpeakerAliases" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_SpeakerAliases" PRIMARY KEY AUTOINCREMENT, "SpeakerIdentityId" INTEGER NOT NULL, "Name" TEXT NOT NULL, CONSTRAINT "FK_SpeakerAliases_SpeakerIdentities_SpeakerIdentityId" FOREIGN KEY ("SpeakerIdentityId") REFERENCES "SpeakerIdentities" ("Id") ON DELETE CASCADE ); """, cancellationToken); await context.Database.ExecuteSqlRawAsync( """ CREATE UNIQUE INDEX IF NOT EXISTS "IX_SpeakerAliases_SpeakerIdentityId_Name" ON "SpeakerAliases" ("SpeakerIdentityId", "Name"); """, cancellationToken); } private static async Task EnsureSpeakerIdentityTimestampColumnsAsync( SpeakerIdentityDbContext context, CancellationToken cancellationToken) { const string defaultTimestamp = "1970-01-01T00:00:00.0000000+00:00"; var hasCreatedAt = await HasColumnAsync(context, "SpeakerIdentities", "CreatedAt", cancellationToken); if (!hasCreatedAt) { await context.Database.ExecuteSqlRawAsync( $""" ALTER TABLE "SpeakerIdentities" ADD COLUMN "CreatedAt" TEXT NOT NULL DEFAULT '{defaultTimestamp}'; """, cancellationToken); } var hasUpdatedAt = await HasColumnAsync(context, "SpeakerIdentities", "UpdatedAt", cancellationToken); if (!hasUpdatedAt) { await context.Database.ExecuteSqlRawAsync( $""" ALTER TABLE "SpeakerIdentities" ADD COLUMN "UpdatedAt" TEXT NOT NULL DEFAULT '{defaultTimestamp}'; """, cancellationToken); await context.Database.ExecuteSqlRawAsync( $""" UPDATE "SpeakerIdentities" SET "UpdatedAt" = "CreatedAt" WHERE "UpdatedAt" = '{defaultTimestamp}'; """, cancellationToken); } } private static async Task HasColumnAsync( SpeakerIdentityDbContext context, string tableName, string columnName, CancellationToken cancellationToken) { var connection = context.Database.GetDbConnection(); var shouldClose = connection.State == System.Data.ConnectionState.Closed; if (shouldClose) { await connection.OpenAsync(cancellationToken); } try { await using var command = connection.CreateCommand(); command.CommandText = $"""PRAGMA table_info("{tableName}")"""; await using var reader = await command.ExecuteReaderAsync(cancellationToken); while (await reader.ReadAsync(cancellationToken)) { if (string.Equals(reader.GetString(1), columnName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } finally { if (shouldClose) { await connection.CloseAsync(); } } } }