diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index 13f316e2cc6..44faecee704 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -26,9 +26,13 @@ import static com.google.cloud.spanner.connection.ConnectionProperties.CREDENTIALS_URL; import static com.google.cloud.spanner.connection.ConnectionProperties.DATABASE_ROLE; import static com.google.cloud.spanner.connection.ConnectionProperties.DATA_BOOST_ENABLED; +import static com.google.cloud.spanner.connection.ConnectionProperties.DCP_INITIAL_CHANNELS; +import static com.google.cloud.spanner.connection.ConnectionProperties.DCP_MAX_CHANNELS; +import static com.google.cloud.spanner.connection.ConnectionProperties.DCP_MIN_CHANNELS; import static com.google.cloud.spanner.connection.ConnectionProperties.DIALECT; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_API_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_DIRECT_ACCESS; +import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_DYNAMIC_CHANNEL_POOL; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_END_TO_END_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS; @@ -155,6 +159,10 @@ public class ConnectionOptions { static final Integer DEFAULT_MIN_SESSIONS = null; static final Integer DEFAULT_MAX_SESSIONS = null; static final Integer DEFAULT_NUM_CHANNELS = null; + static final Boolean DEFAULT_ENABLE_DYNAMIC_CHANNEL_POOL = null; + static final Integer DEFAULT_DCP_MIN_CHANNELS = null; + static final Integer DEFAULT_DCP_MAX_CHANNELS = null; + static final Integer DEFAULT_DCP_INITIAL_CHANNELS = null; static final String DEFAULT_ENDPOINT = null; static final String DEFAULT_CHANNEL_PROVIDER = null; static final String DEFAULT_DATABASE_ROLE = null; @@ -252,6 +260,18 @@ public class ConnectionOptions { /** Name of the 'numChannels' connection property. */ public static final String NUM_CHANNELS_PROPERTY_NAME = "numChannels"; + /** Name of the 'enableDynamicChannelPool' connection property. */ + public static final String ENABLE_DYNAMIC_CHANNEL_POOL_PROPERTY_NAME = "enableDynamicChannelPool"; + + /** Name of the 'dcpMinChannels' connection property. */ + public static final String DCP_MIN_CHANNELS_PROPERTY_NAME = "dcpMinChannels"; + + /** Name of the 'dcpMaxChannels' connection property. */ + public static final String DCP_MAX_CHANNELS_PROPERTY_NAME = "dcpMaxChannels"; + + /** Name of the 'dcpInitialChannels' connection property. */ + public static final String DCP_INITIAL_CHANNELS_PROPERTY_NAME = "dcpInitialChannels"; + /** Name of the 'endpoint' connection property. */ public static final String ENDPOINT_PROPERTY_NAME = "endpoint"; @@ -991,6 +1011,26 @@ public Integer getNumChannels() { return getInitialConnectionPropertyValue(NUM_CHANNELS); } + /** Whether dynamic channel pooling is enabled for this connection. */ + public Boolean isEnableDynamicChannelPool() { + return getInitialConnectionPropertyValue(ENABLE_DYNAMIC_CHANNEL_POOL); + } + + /** The minimum number of channels in the dynamic channel pool. */ + public Integer getDcpMinChannels() { + return getInitialConnectionPropertyValue(DCP_MIN_CHANNELS); + } + + /** The maximum number of channels in the dynamic channel pool. */ + public Integer getDcpMaxChannels() { + return getInitialConnectionPropertyValue(DCP_MAX_CHANNELS); + } + + /** The initial number of channels in the dynamic channel pool. */ + public Integer getDcpInitialChannels() { + return getInitialConnectionPropertyValue(DCP_INITIAL_CHANNELS); + } + /** Calls the getChannelProvider() method from the supplied class. */ public TransportChannelProvider getChannelProvider() { String channelProvider = getInitialConnectionPropertyValue(CHANNEL_PROVIDER); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java index 629fe7c69da..5fa678afef5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java @@ -29,6 +29,9 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.CREDENTIALS_PROVIDER_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DATABASE_ROLE_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DATA_BOOST_ENABLED_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.DCP_INITIAL_CHANNELS_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.DCP_MAX_CHANNELS_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.DCP_MIN_CHANNELS_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DDL_IN_TRANSACTION_MODE_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_AUTOCOMMIT; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_AUTO_BATCH_DML; @@ -42,10 +45,14 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CREDENTIALS; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DATABASE_ROLE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DATA_BOOST_ENABLED; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DCP_INITIAL_CHANNELS; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DCP_MAX_CHANNELS; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DCP_MIN_CHANNELS; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DDL_IN_TRANSACTION_MODE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DEFAULT_SEQUENCE_KIND; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_API_TRACING; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_DYNAMIC_CHANNEL_POOL; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_END_TO_END_TRACING; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_EXTENDED_TRACING; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT; @@ -75,6 +82,7 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DIALECT_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_API_TRACING_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_DYNAMIC_CHANNEL_POOL_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_END_TO_END_TRACING_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY; @@ -443,6 +451,45 @@ public class ConnectionProperties { DEFAULT_NUM_CHANNELS, NonNegativeIntegerConverter.INSTANCE, Context.STARTUP); + static final ConnectionProperty ENABLE_DYNAMIC_CHANNEL_POOL = + create( + ENABLE_DYNAMIC_CHANNEL_POOL_PROPERTY_NAME, + "Enable dynamic channel pooling for automatic gRPC channel scaling. When enabled, the " + + "client will automatically scale the number of channels based on load. Setting " + + "numChannels will disable dynamic channel pooling even if this is set to true. " + + "The default is currently false (disabled), but this may change to true in a " + + "future version. Set this property explicitly to ensure consistent behavior.", + DEFAULT_ENABLE_DYNAMIC_CHANNEL_POOL, + BOOLEANS, + BooleanConverter.INSTANCE, + Context.STARTUP); + static final ConnectionProperty DCP_MIN_CHANNELS = + create( + DCP_MIN_CHANNELS_PROPERTY_NAME, + "The minimum number of channels in the dynamic channel pool. Only used when " + + "enableDynamicChannelPool is true. The default is " + + "SpannerOptions.DEFAULT_DYNAMIC_POOL_MIN_CHANNELS (2).", + DEFAULT_DCP_MIN_CHANNELS, + NonNegativeIntegerConverter.INSTANCE, + Context.STARTUP); + static final ConnectionProperty DCP_MAX_CHANNELS = + create( + DCP_MAX_CHANNELS_PROPERTY_NAME, + "The maximum number of channels in the dynamic channel pool. Only used when " + + "enableDynamicChannelPool is true. The default is " + + "SpannerOptions.DEFAULT_DYNAMIC_POOL_MAX_CHANNELS (10).", + DEFAULT_DCP_MAX_CHANNELS, + NonNegativeIntegerConverter.INSTANCE, + Context.STARTUP); + static final ConnectionProperty DCP_INITIAL_CHANNELS = + create( + DCP_INITIAL_CHANNELS_PROPERTY_NAME, + "The initial number of channels in the dynamic channel pool. Only used when " + + "enableDynamicChannelPool is true. The default is " + + "SpannerOptions.DEFAULT_DYNAMIC_POOL_INITIAL_SIZE (4).", + DEFAULT_DCP_INITIAL_CHANNELS, + NonNegativeIntegerConverter.INSTANCE, + Context.STARTUP); static final ConnectionProperty CHANNEL_PROVIDER = create( CHANNEL_PROVIDER_PROPERTY_NAME, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index e78a646f070..e4912b8e4f2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -17,12 +17,14 @@ package com.google.cloud.spanner.connection; import com.google.cloud.NoCredentials; +import com.google.cloud.grpc.GcpManagedChannelOptions.GcpChannelPoolOptions; import com.google.cloud.spanner.DecodeMode; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.SessionPoolOptions; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -152,6 +154,10 @@ static class SpannerPoolKey { private final CredentialsKey credentialsKey; private final SessionPoolOptions sessionPoolOptions; private final Integer numChannels; + private final Boolean enableDynamicChannelPool; + private final Integer dcpMinChannels; + private final Integer dcpMaxChannels; + private final Integer dcpInitialChannels; private final boolean usePlainText; private final String userAgent; private final String databaseRole; @@ -190,6 +196,10 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { ? SessionPoolOptions.newBuilder().build() : options.getSessionPoolOptions(); this.numChannels = options.getNumChannels(); + this.enableDynamicChannelPool = options.isEnableDynamicChannelPool(); + this.dcpMinChannels = options.getDcpMinChannels(); + this.dcpMaxChannels = options.getDcpMaxChannels(); + this.dcpInitialChannels = options.getDcpInitialChannels(); this.usePlainText = options.isUsePlainText(); this.userAgent = options.getUserAgent(); this.routeToLeader = options.isRouteToLeader(); @@ -217,6 +227,10 @@ public boolean equals(Object o) { && Objects.equals(this.credentialsKey, other.credentialsKey) && Objects.equals(this.sessionPoolOptions, other.sessionPoolOptions) && Objects.equals(this.numChannels, other.numChannels) + && Objects.equals(this.enableDynamicChannelPool, other.enableDynamicChannelPool) + && Objects.equals(this.dcpMinChannels, other.dcpMinChannels) + && Objects.equals(this.dcpMaxChannels, other.dcpMaxChannels) + && Objects.equals(this.dcpInitialChannels, other.dcpInitialChannels) && Objects.equals(this.databaseRole, other.databaseRole) && Objects.equals(this.usePlainText, other.usePlainText) && Objects.equals(this.userAgent, other.userAgent) @@ -243,6 +257,10 @@ public int hashCode() { this.credentialsKey, this.sessionPoolOptions, this.numChannels, + this.enableDynamicChannelPool, + this.dcpMinChannels, + this.dcpMaxChannels, + this.dcpInitialChannels, this.usePlainText, this.databaseRole, this.userAgent, @@ -403,6 +421,50 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { if (key.numChannels != null) { builder.setNumChannels(key.numChannels); } + // Configure Dynamic Channel Pooling (DCP) based on explicit user setting. + // Note: Setting numChannels disables DCP even if enableDynamicChannelPool is true. + if (key.enableDynamicChannelPool != null && key.numChannels == null) { + if (Boolean.TRUE.equals(key.enableDynamicChannelPool)) { + builder.enableDynamicChannelPool(); + // Build custom GcpChannelPoolOptions if any DCP-specific options are set. + if (key.dcpMinChannels != null + || key.dcpMaxChannels != null + || key.dcpInitialChannels != null) { + // Build GcpChannelPoolOptions from scratch with custom values or Spanner defaults. + // Note: GcpChannelPoolOptions does not have a toBuilder() method, so we must + // construct from scratch using SpannerOptions defaults for unspecified values. + int minChannels = + key.dcpMinChannels != null + ? key.dcpMinChannels + : SpannerOptions.DEFAULT_DYNAMIC_POOL_MIN_CHANNELS; + int maxChannels = + key.dcpMaxChannels != null + ? key.dcpMaxChannels + : SpannerOptions.DEFAULT_DYNAMIC_POOL_MAX_CHANNELS; + int initChannels = + key.dcpInitialChannels != null + ? key.dcpInitialChannels + : SpannerOptions.DEFAULT_DYNAMIC_POOL_INITIAL_SIZE; + GcpChannelPoolOptions poolOptions = + GcpChannelPoolOptions.newBuilder() + .setMinSize(minChannels) + .setMaxSize(maxChannels) + .setInitSize(initChannels) + .setDynamicScaling( + SpannerOptions.DEFAULT_DYNAMIC_POOL_MIN_RPC, + SpannerOptions.DEFAULT_DYNAMIC_POOL_MAX_RPC, + SpannerOptions.DEFAULT_DYNAMIC_POOL_SCALE_DOWN_INTERVAL) + .setAffinityKeyLifetime(SpannerOptions.DEFAULT_DYNAMIC_POOL_AFFINITY_KEY_LIFETIME) + .setCleanupInterval(SpannerOptions.DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL) + .build(); + builder.setGcpChannelPoolOptions(poolOptions); + } + } else { + // Explicitly disable DCP when enableDynamicChannelPool=false. + // This ensures consistent behavior even if the default changes in the future. + builder.disableDynamicChannelPool(); + } + } if (options.getChannelProvider() != null) { builder.setChannelProvider(options.getChannelProvider()); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java index 18fd5cb6143..f745e9ad63e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java @@ -1418,4 +1418,113 @@ public void testUniverseDomain() { connection.close(); } + + @Test + public void testEnableDynamicChannelPool() { + // Default value + assertNull( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentials(NoCredentials.getInstance()) + .build() + .isEnableDynamicChannelPool()); + // Enabled + assertTrue( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?enableDynamicChannelPool=true") + .setCredentials(NoCredentials.getInstance()) + .build() + .isEnableDynamicChannelPool()); + } + + @Test + public void testDisableDynamicChannelPool() { + assertFalse( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?enableDynamicChannelPool=false") + .setCredentials(NoCredentials.getInstance()) + .build() + .isEnableDynamicChannelPool()); + } + + @Test + public void testDcpMinChannels() { + // Default value + assertNull( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpMinChannels()); + // Custom value + assertEquals( + Integer.valueOf(3), + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?dcpMinChannels=3") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpMinChannels()); + } + + @Test + public void testDcpMaxChannels() { + // Default value + assertNull( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpMaxChannels()); + // Custom value + assertEquals( + Integer.valueOf(15), + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?dcpMaxChannels=15") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpMaxChannels()); + } + + @Test + public void testDcpInitialChannels() { + // Default value + assertNull( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpInitialChannels()); + // Custom value + assertEquals( + Integer.valueOf(5), + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build() + .getDcpInitialChannels()); + } + + @Test + public void testDcpWithAllOptions() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database" + + "?enableDynamicChannelPool=true;dcpMinChannels=3;dcpMaxChannels=15;dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build(); + assertTrue(options.isEnableDynamicChannelPool()); + assertEquals(Integer.valueOf(3), options.getDcpMinChannels()); + assertEquals(Integer.valueOf(15), options.getDcpMaxChannels()); + assertEquals(Integer.valueOf(5), options.getDcpInitialChannels()); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java index 973dc5ea17c..b03288354b2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java @@ -635,4 +635,140 @@ public void testOpenTelemetry() { spanner2 = pool.getSpanner(optionsOpenTelemetry3, connection2); assertNotEquals(spanner1, spanner2); } + + @Test + public void testDynamicChannelPoolSettings() { + SpannerPoolKey keyWithoutDcp = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/p/instances/i/databases/d") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpEnabled = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpDisabled = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=false") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpAndMinChannels = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true;dcpMinChannels=3") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpAndMaxChannels = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true;dcpMaxChannels=15") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpAndInitialChannels = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true;dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build()); + + // DCP settings should affect the SpannerPoolKey + assertNotEquals(keyWithoutDcp, keyWithDcpEnabled); + assertNotEquals(keyWithoutDcp, keyWithDcpDisabled); + assertNotEquals(keyWithDcpEnabled, keyWithDcpDisabled); + + // Different channel settings should create different keys + assertNotEquals(keyWithDcpEnabled, keyWithDcpAndMinChannels); + assertNotEquals(keyWithDcpEnabled, keyWithDcpAndMaxChannels); + assertNotEquals(keyWithDcpEnabled, keyWithDcpAndInitialChannels); + + // Same configuration should create equal keys + assertEquals( + keyWithDcpEnabled, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true") + .setCredentials(NoCredentials.getInstance()) + .build())); + assertEquals( + keyWithDcpAndMinChannels, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=true;dcpMinChannels=3") + .setCredentials(NoCredentials.getInstance()) + .build())); + } + + @Test + public void testDynamicChannelPoolWithAllSettings() { + SpannerPoolKey keyWithAllDcpSettings = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d" + + "?enableDynamicChannelPool=true;dcpMinChannels=3;dcpMaxChannels=15;dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDifferentMaxChannels = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d" + + "?enableDynamicChannelPool=true;dcpMinChannels=3;dcpMaxChannels=20;dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build()); + + assertNotEquals(keyWithAllDcpSettings, keyWithDifferentMaxChannels); + + // Same configuration should be equal + assertEquals( + keyWithAllDcpSettings, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d" + + "?enableDynamicChannelPool=true;dcpMinChannels=3;dcpMaxChannels=15;dcpInitialChannels=5") + .setCredentials(NoCredentials.getInstance()) + .build())); + } + + @Test + public void testExplicitlyDisabledDynamicChannelPool() { + SpannerPoolKey keyWithoutDcpSetting = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/p/instances/i/databases/d") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithDcpExplicitlyDisabled = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=false") + .setCredentials(NoCredentials.getInstance()) + .build()); + + // Keys should be different because one has explicit false and one has null (default) + assertNotEquals(keyWithoutDcpSetting, keyWithDcpExplicitlyDisabled); + + // Verify the explicit false setting is preserved + assertEquals( + keyWithDcpExplicitlyDisabled, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableDynamicChannelPool=false") + .setCredentials(NoCredentials.getInstance()) + .build())); + } }