Skip to content

Commit 3bb3109

Browse files
committed
Rewrite NpgsqlDatabaseCreator.Exists() to do SELECT 1 (#3648)
Fixes #3646 (cherry picked from commit 3e175da)
1 parent 28dfd7c commit 3bb3109

2 files changed

Lines changed: 80 additions & 72 deletions

File tree

src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
1313
public class NpgsqlDatabaseCreator(
1414
RelationalDatabaseCreatorDependencies dependencies,
1515
INpgsqlRelationalConnection connection,
16-
IRawSqlCommandBuilder rawSqlCommandBuilder,
17-
IRelationalConnectionDiagnosticsLogger connectionLogger)
16+
IRawSqlCommandBuilder rawSqlCommandBuilder)
1817
: RelationalDatabaseCreator(dependencies)
1918
{
2019
/// <summary>
@@ -175,7 +174,41 @@ private IReadOnlyList<MigrationCommand> CreateCreateOperations()
175174
/// doing so can result in application failures when updating to a new Entity Framework Core release.
176175
/// </summary>
177176
public override bool Exists()
178-
=> Exists(async: false).GetAwaiter().GetResult();
177+
=> Dependencies.ExecutionStrategy.Execute(() =>
178+
{
179+
using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
180+
var opened = false;
181+
182+
try
183+
{
184+
connection.Open(errorsExpected: true);
185+
opened = true;
186+
187+
rawSqlCommandBuilder
188+
.Build("SELECT 1")
189+
.ExecuteNonQuery(
190+
new RelationalCommandParameterObject(
191+
connection,
192+
parameterValues: null,
193+
readerColumns: null,
194+
Dependencies.CurrentContext.Context,
195+
Dependencies.CommandLogger,
196+
CommandSource.Migrations));
197+
198+
return true;
199+
}
200+
catch (Exception e) when (IsDoesNotExist(e))
201+
{
202+
return false;
203+
}
204+
finally
205+
{
206+
if (opened)
207+
{
208+
connection.Close();
209+
}
210+
}
211+
});
179212

180213
/// <summary>
181214
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -184,85 +217,61 @@ public override bool Exists()
184217
/// doing so can result in application failures when updating to a new Entity Framework Core release.
185218
/// </summary>
186219
public override Task<bool> ExistsAsync(CancellationToken cancellationToken = default)
187-
=> Exists(async: true, cancellationToken);
188-
189-
private async Task<bool> Exists(bool async, CancellationToken cancellationToken = default)
190-
{
191-
var logger = connectionLogger;
192-
var startTime = DateTimeOffset.UtcNow;
193-
194-
var interceptionResult = async
195-
? await logger.ConnectionOpeningAsync(connection, startTime, cancellationToken).ConfigureAwait(false)
196-
: logger.ConnectionOpening(connection, startTime);
197-
198-
if (interceptionResult.IsSuppressed)
220+
=> Dependencies.ExecutionStrategy.ExecuteAsync(async ct =>
199221
{
200-
// If the connection attempt was suppressed by an interceptor, assume that the interceptor took care of all the opening
201-
// details, and the database exists.
202-
return true;
203-
}
204-
205-
// When checking whether a database exists, pooling must be off, otherwise we may
206-
// attempt to reuse a pooled connection, which may be broken (this happened in the tests).
207-
// If Pooling is off, but Multiplexing is on - NpgsqlConnectionStringBuilder.Validate will throw,
208-
// so we turn off Multiplexing as well.
209-
var unpooledCsb = new NpgsqlConnectionStringBuilder(connection.ConnectionString) { Pooling = false, Multiplexing = false };
222+
using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
223+
var opened = false;
210224

211-
using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
212-
var unpooledRelationalConnection = connection.CloneWith(unpooledCsb.ToString());
213-
try
214-
{
215-
if (async)
225+
try
216226
{
217-
await unpooledRelationalConnection.OpenAsync(errorsExpected: true, cancellationToken: cancellationToken)
227+
await connection.OpenAsync(cancellationToken, errorsExpected: true).ConfigureAwait(false);
228+
opened = true;
229+
230+
await rawSqlCommandBuilder
231+
.Build("SELECT 1")
232+
.ExecuteNonQueryAsync(
233+
new RelationalCommandParameterObject(
234+
connection,
235+
parameterValues: null,
236+
readerColumns: null,
237+
Dependencies.CurrentContext.Context,
238+
Dependencies.CommandLogger,
239+
CommandSource.Migrations),
240+
cancellationToken)
218241
.ConfigureAwait(false);
242+
243+
return true;
219244
}
220-
else
245+
catch (Exception e) when (IsDoesNotExist(e))
221246
{
222-
unpooledRelationalConnection.Open(errorsExpected: true);
247+
return false;
223248
}
224-
225-
return true;
226-
}
227-
catch (PostgresException e)
228-
{
229-
if (IsDoesNotExist(e))
249+
finally
230250
{
231-
return false;
251+
if (opened)
252+
{
253+
await connection.CloseAsync().ConfigureAwait(false);
254+
}
232255
}
256+
}, cancellationToken);
233257

234-
throw;
235-
}
236-
catch (NpgsqlException e) when (
237-
// This can happen when Npgsql attempts to connect to multiple hosts
238-
e.InnerException is AggregateException ae && ae.InnerExceptions.Any(ie => ie is PostgresException pe && IsDoesNotExist(pe)))
239-
{
240-
return false;
241-
}
242-
catch (NpgsqlException e) when (
243-
e.InnerException is IOException { InnerException: SocketException { SocketErrorCode: SocketError.ConnectionReset } })
258+
private static bool IsDoesNotExist(Exception exception)
259+
=> exception switch
244260
{
261+
// Login failed is thrown when database does not exist (See Issue #776)
262+
PostgresException { SqlState: "3D000" }
263+
=> true,
264+
265+
// This can happen when Npgsql attempts to connect to multiple hosts
266+
NpgsqlException { InnerException: AggregateException ae } when ae.InnerExceptions.Any(ie => ie is PostgresException { SqlState: "3D000" })
267+
=> true,
268+
245269
// Pretty awful hack around #104
246-
return false;
247-
}
248-
finally
249-
{
250-
if (async)
251-
{
252-
await unpooledRelationalConnection.CloseAsync().ConfigureAwait(false);
253-
await unpooledRelationalConnection.DisposeAsync().ConfigureAwait(false);
254-
}
255-
else
256-
{
257-
unpooledRelationalConnection.Close();
258-
unpooledRelationalConnection.Dispose();
259-
}
260-
}
261-
}
270+
NpgsqlException { InnerException: IOException { InnerException: SocketException { SocketErrorCode: SocketError.ConnectionReset } } }
271+
=> true,
262272

263-
// Login failed is thrown when database does not exist (See Issue #776)
264-
private static bool IsDoesNotExist(PostgresException exception)
265-
=> exception.SqlState == "3D000";
273+
_ => false
274+
};
266275

267276
/// <summary>
268277
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to

test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,8 @@ public class TestDatabaseCreator : NpgsqlDatabaseCreator
646646
public TestDatabaseCreator(
647647
RelationalDatabaseCreatorDependencies dependencies,
648648
INpgsqlRelationalConnection connection,
649-
IRawSqlCommandBuilder rawSqlCommandBuilder,
650-
IRelationalConnectionDiagnosticsLogger connectionLogger)
651-
: base(dependencies, connection, rawSqlCommandBuilder, connectionLogger)
649+
IRawSqlCommandBuilder rawSqlCommandBuilder)
650+
: base(dependencies, connection, rawSqlCommandBuilder)
652651
{
653652
}
654653
public bool HasTablesBase()

0 commit comments

Comments
 (0)