Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoke connection interceptor before tweaking connection string in Exists() #3349

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 37 additions & 37 deletions src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,13 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class NpgsqlDatabaseCreator : RelationalDatabaseCreator
{
private readonly INpgsqlRelationalConnection _connection;
private readonly IRawSqlCommandBuilder _rawSqlCommandBuilder;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public NpgsqlDatabaseCreator(
public class NpgsqlDatabaseCreator(
RelationalDatabaseCreatorDependencies dependencies,
INpgsqlRelationalConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder)
: base(dependencies)
{
_connection = connection;
_rawSqlCommandBuilder = rawSqlCommandBuilder;
}

IRawSqlCommandBuilder rawSqlCommandBuilder,
IRelationalConnectionDiagnosticsLogger connectionLogger)
: RelationalDatabaseCreator(dependencies)
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -55,7 +41,7 @@ public NpgsqlDatabaseCreator(
/// </summary>
public override void Create()
{
using (var masterConnection = _connection.CreateAdminConnection())
using (var masterConnection = connection.CreateAdminConnection())
{
try
{
Expand All @@ -82,7 +68,7 @@ public override void Create()
/// </summary>
public override async Task CreateAsync(CancellationToken cancellationToken = default)
{
var masterConnection = _connection.CreateAdminConnection();
var masterConnection = connection.CreateAdminConnection();
await using (masterConnection.ConfigureAwait(false))
{
try
Expand Down Expand Up @@ -112,7 +98,7 @@ await Dependencies.MigrationCommandExecutor
public override bool HasTables()
=> Dependencies.ExecutionStrategy
.Execute(
_connection,
connection,
connection => (bool)CreateHasTablesCommand()
.ExecuteScalar(
new RelationalCommandParameterObject(
Expand All @@ -130,7 +116,7 @@ public override bool HasTables()
/// </summary>
public override Task<bool> HasTablesAsync(CancellationToken cancellationToken = default)
=> Dependencies.ExecutionStrategy.ExecuteAsync(
_connection,
connection,
async (connection, ct) => (bool)(await CreateHasTablesCommand()
.ExecuteScalarAsync(
new RelationalCommandParameterObject(
Expand All @@ -142,7 +128,7 @@ public override Task<bool> HasTablesAsync(CancellationToken cancellationToken =
cancellationToken: ct).ConfigureAwait(false))!, cancellationToken);

private IRelationalCommand CreateHasTablesCommand()
=> _rawSqlCommandBuilder
=> rawSqlCommandBuilder
.Build(
"""
SELECT CASE WHEN COUNT(*) = 0 THEN FALSE ELSE TRUE END
Expand Down Expand Up @@ -173,7 +159,7 @@ private IReadOnlyList<MigrationCommand> CreateCreateOperations()
[
new NpgsqlCreateDatabaseOperation
{
Name = _connection.DbConnection.Database,
Name = connection.DbConnection.Database,
Template = designTimeModel.GetDatabaseTemplate(),
Collation = designTimeModel.GetCollation(),
Tablespace = designTimeModel.GetTablespace()
Expand Down Expand Up @@ -201,15 +187,29 @@ public override Task<bool> ExistsAsync(CancellationToken cancellationToken = def

private async Task<bool> Exists(bool async, CancellationToken cancellationToken = default)
{
var logger = connectionLogger;
var startTime = DateTimeOffset.UtcNow;

var interceptionResult = async
? await logger.ConnectionOpeningAsync(connection, startTime, cancellationToken).ConfigureAwait(false)
: logger.ConnectionOpening(connection, startTime);

if (interceptionResult.IsSuppressed)
{
// If the connection attempt was suppressed by an interceptor, assume that the interceptor took care of all the opening
// details, and the database exists.
return true;
}

// When checking whether a database exists, pooling must be off, otherwise we may
// attempt to reuse a pooled connection, which may be broken (this happened in the tests).
// If Pooling is off, but Multiplexing is on - NpgsqlConnectionStringBuilder.Validate will throw,
// so we turn off Multiplexing as well.
var unpooledCsb = new NpgsqlConnectionStringBuilder(_connection.ConnectionString) { Pooling = false, Multiplexing = false };
var unpooledCsb = new NpgsqlConnectionStringBuilder(connection.ConnectionString) { Pooling = false, Multiplexing = false };

using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
var unpooledRelationalConnection =
await _connection.CloneWith(unpooledCsb.ToString(), async, cancellationToken).ConfigureAwait(false);
await connection.CloneWith(unpooledCsb.ToString(), async, cancellationToken).ConfigureAwait(false);

try
{
Expand Down Expand Up @@ -273,7 +273,7 @@ private static bool IsDoesNotExist(PostgresException exception)
/// </summary>
public override void Delete()
{
switch (_connection.DataSource)
switch (connection.DataSource)
{
case NpgsqlDataSource dataSource:
dataSource.Clear();
Expand All @@ -283,7 +283,7 @@ public override void Delete()
break;
}

using (var masterConnection = _connection.CreateAdminConnection())
using (var masterConnection = connection.CreateAdminConnection())
{
Dependencies.MigrationCommandExecutor
.ExecuteNonQuery(CreateDropCommands(), masterConnection);
Expand All @@ -298,7 +298,7 @@ public override void Delete()
/// </summary>
public override async Task DeleteAsync(CancellationToken cancellationToken = default)
{
switch (_connection.DataSource)
switch (connection.DataSource)
{
case NpgsqlDataSource dataSource:
// TODO: Do this asynchronously once https://github.com/npgsql/npgsql/issues/4499 is done
Expand All @@ -309,7 +309,7 @@ public override async Task DeleteAsync(CancellationToken cancellationToken = def
break;
}

var masterConnection = _connection.CreateAdminConnection();
var masterConnection = connection.CreateAdminConnection();
await using (masterConnection)
{
await Dependencies.MigrationCommandExecutor
Expand Down Expand Up @@ -339,15 +339,15 @@ public override void CreateTables()

try
{
Dependencies.MigrationCommandExecutor.ExecuteNonQuery(commands, _connection);
Dependencies.MigrationCommandExecutor.ExecuteNonQuery(commands, connection);
}
catch (PostgresException e) when (e is { SqlState: "23505", ConstraintName: "pg_type_typname_nsp_index" })
{
// This occurs when two connections are trying to create the same database concurrently
// (happens in the tests). Simply ignore the error.
}

if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection)
if (reloadTypes && connection.DbConnection is NpgsqlConnection npgsqlConnection)
{
npgsqlConnection.Open();
try
Expand Down Expand Up @@ -375,7 +375,7 @@ public override async Task CreateTablesAsync(CancellationToken cancellationToken

try
{
await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, _connection, cancellationToken)
await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, connection, cancellationToken)
.ConfigureAwait(false);
}
catch (PostgresException e) when (e is { SqlState: "23505", ConstraintName: "pg_type_typname_nsp_index" })
Expand All @@ -389,7 +389,7 @@ await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, _conn
.OfType<AlterDatabaseOperation>()
.Any(o => o.GetPostgresExtensions().Any() || o.GetPostgresEnums().Any() || o.GetPostgresRanges().Any());

if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection)
if (reloadTypes && connection.DbConnection is NpgsqlConnection npgsqlConnection)
{
await npgsqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
try
Expand All @@ -409,7 +409,7 @@ private IReadOnlyList<MigrationCommand> CreateDropCommands()
{
// TODO Check DbConnection.Database always gives us what we want
// Issue #775
new NpgsqlDropDatabaseOperation { Name = _connection.DbConnection.Database }
new NpgsqlDropDatabaseOperation { Name = connection.DbConnection.Database }
};

return Dependencies.MigrationsSqlGenerator.Generate(operations);
Expand All @@ -422,5 +422,5 @@ private static void ClearAllPools()
// Clear connection pool for the database connection since after the 'create database' call, a previously
// invalid connection may now be valid.
private void ClearPool()
=> NpgsqlConnection.ClearPool((NpgsqlConnection)_connection.DbConnection);
=> NpgsqlConnection.ClearPool((NpgsqlConnection)connection.DbConnection);
}
5 changes: 3 additions & 2 deletions test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -632,8 +632,9 @@ public class Blog
public class TestDatabaseCreator(
RelationalDatabaseCreatorDependencies dependencies,
INpgsqlRelationalConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder)
: NpgsqlDatabaseCreator(dependencies, connection, rawSqlCommandBuilder)
IRawSqlCommandBuilder rawSqlCommandBuilder,
IRelationalConnectionDiagnosticsLogger connectionLogger)
: NpgsqlDatabaseCreator(dependencies, connection, rawSqlCommandBuilder, connectionLogger)
{
public bool HasTablesBase()
=> HasTables();
Expand Down
Loading