From 922c0896a5bc84ff3fd387946520a843843baa4b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 23 Sep 2024 23:33:58 +0200 Subject: [PATCH] Catch exception instead of trying to check if the history repository exists (#3294) Fixes #2878 --- .../Internal/NpgsqlHistoryRepository.cs | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index ddc8d3be7..b0469daa9 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -75,22 +75,8 @@ private RelationalCommandParameterObject CreateRelationalCommandParameters() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string ExistsSql - { - get - { - var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); - - return - $""" -SELECT EXISTS ( - SELECT 1 FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace - WHERE n.nspname={stringTypeMapping.GenerateSqlLiteral(TableSchema ?? "public")} AND - c.relname={stringTypeMapping.GenerateSqlLiteral(TableName)} -) -"""; - } - } + => throw new UnreachableException( + "We should not be checking for the existence of the history table, but rather creating it and catching exceptions (see below)"); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -194,6 +180,58 @@ public override string GetEndIfScript() END $EF$; """; + /// + /// Calls the base implementation, but catches "table not found" exceptions; we do this rather than try to detect whether the + /// migration table already exists (see override below), since it's difficult to reliably check if the + /// migration history table exists or not (because user may set PG search_path, which determines unqualified tables + /// references when creating, selecting). + /// + public override IReadOnlyList GetAppliedMigrations() + { + try + { + return base.GetAppliedMigrations(); + } + catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + { + return []; + } + } + + /// + /// Calls the base implementation, but catches "table not found" exceptions; we do this rather than try to detect whether the + /// migration table already exists (see override below), since it's difficult to reliably check if the + /// migration history table exists or not (because user may set PG search_path, which determines unqualified tables + /// references when creating, selecting). + /// + public override async Task> GetAppliedMigrationsAsync(CancellationToken cancellationToken = default) + { + try + { + return await base.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); + } + catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + { + return []; + } + } + + /// + /// Always returns for PostgreSQL - it's difficult to reliably check if the migration history table + /// exists or not (because user may set PG search_path, which determines unqualified tables references when creating, + /// selecting). So we instead catch the "table doesn't exist" exceptions instead. + /// + public override bool Exists() + => true; + + /// + /// Always returns for PostgreSQL - it's difficult to reliably check if the migration history table + /// exists or not (because user may set PG search_path, which determines unqualified tables references when creating, + /// selecting). So we instead catch the "table doesn't exist" exceptions instead. + /// + public override Task ExistsAsync(CancellationToken cancellationToken = default) + => Task.FromResult(true); + private sealed class NpgsqlMigrationDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock { ///