Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,6 @@ ralph.log

# AI agent session state
PROMPT.md

# Local-only docker compose overrides (e.g. port remaps for host conflicts)
docker-compose-*.local.yaml
2 changes: 1 addition & 1 deletion bugfixes/.current-bug
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0003-pump-span-leak-exception-paths
0004-spanner-emulator-detection
Empty file.
1 change: 1 addition & 0 deletions bugfixes/0004-spanner-emulator-detection/.issue-number
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4162
115 changes: 115 additions & 0 deletions bugfixes/0004-spanner-emulator-detection/bugfix.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Google.Api.Gax;
using Google.Cloud.Spanner.Data;

namespace Paramore.Brighter.Spanner;
Expand All @@ -14,7 +15,13 @@ namespace Paramore.Brighter.Spanner;
public class SpannerConnectionProvider(IAmARelationalDatabaseConfiguration configuration)
: RelationalDbConnectionProvider
{
private readonly string _connectionString = configuration.ConnectionString;
// Enable emulator detection so the connection routes to SPANNER_EMULATOR_HOST when it is set,
// and behaves as normal production otherwise. The keyword serialises into the connection string.
private readonly string _connectionString =
new SpannerConnectionStringBuilder(configuration.ConnectionString)
{
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.ConnectionString;

Comment on lines +18 to 25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be done in the IAmARelationalDatabaseConfiguration?

Like

new RelationalDatabaseConfiguration(new SpannerConnectionStringBuilder("MyConnection")
        {
            EmulatorDetection = EmulatorDetection.EmulatorOrProduction
        }.ConnectionString)

/// <inheritdoc />
public override DbConnection GetConnection()
Expand Down
11 changes: 9 additions & 2 deletions src/Paramore.Brighter.Spanner/SpannerUnitOfWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Google.Api.Gax;
using Google.Cloud.Spanner.Data;

namespace Paramore.Brighter.Spanner;
Expand All @@ -20,8 +21,14 @@ namespace Paramore.Brighter.Spanner;
/// <param name="configuration">The configuration containing the connection string for the Spanner database.</param>
public class SpannerUnitOfWork(IAmARelationalDatabaseConfiguration configuration) : RelationalDbTransactionProvider
{
private readonly string _connectionString = configuration.ConnectionString;

// Enable emulator detection so the connection routes to SPANNER_EMULATOR_HOST when it is set,
// and behaves as normal production otherwise. The keyword serialises into the connection string.
private readonly string _connectionString =
new SpannerConnectionStringBuilder(configuration.ConnectionString)
{
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.ConnectionString;

/// <inheritdoc />
public override DbConnection GetConnection()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Data;
using System.Threading.Tasks;
using Google.Cloud.Spanner.Data;
using Paramore.Brighter.Gcp.Tests.Helper;
using Paramore.Brighter.Spanner;
using Xunit;

namespace Paramore.Brighter.Gcp.Tests.Spanner.Connection;

[Trait("Category", "Spanner")]
public class SpannerConnectionProviderEmulatorTests
{
// A bare connection string with no EmulatorDetection keyword — the shape production code
// receives via RelationalDatabaseConfiguration. Built inline (not via Const) so this test
// stays scoped to the provider's own emulator handling even after Const is patched.
private readonly string _bareConnectionString =
$"Data Source=projects/{GatewayFactory.GetProjectId()}/instances/brighter-spanner/databases/brightertests";

[Fact]
public async Task When_a_spanner_connection_provider_opens_a_connection_it_should_honour_the_spanner_emulator()
{
// Arrange
var configuration = new RelationalDatabaseConfiguration(_bareConnectionString);
var provider = new SpannerConnectionProvider(configuration);

// Act
await using var connection = await provider.GetConnectionAsync();

using var command = ((SpannerConnection)connection).CreateSelectCommand("SELECT 1");
var result = (long)(await command.ExecuteScalarAsync())!;

// Assert
Assert.Equal(ConnectionState.Open, connection.State);
Assert.Equal(1L, result);
}
}
14 changes: 12 additions & 2 deletions tests/Paramore.Brighter.Gcp.Tests/Spanner/Const.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
using Paramore.Brighter.Gcp.Tests.Helper;
using Google.Api.Gax;
using Google.Cloud.Spanner.Data;
using Paramore.Brighter.Gcp.Tests.Helper;

namespace Paramore.Brighter.Gcp.Tests.Spanner;

public static class Const
{
public static string ConnectionString => $"Data Source=projects/{GatewayFactory.GetProjectId()}/instances/brighter-spanner/databases/brightertests";
// Embed EmulatorDetection into the connection string so it round-trips through
// RelationalDatabaseConfiguration into the tests' own DDL connections and the outbox/inbox's
// internal connections, routing to SPANNER_EMULATOR_HOST when set instead of demanding ADC.
public static string ConnectionString => new SpannerConnectionStringBuilder(
$"Data Source=projects/{GatewayFactory.GetProjectId()}/instances/brighter-spanner/databases/brightertests")
{
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.ConnectionString;

public const string TablePrefix = "test_";
}
Loading