diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 5c103beca6f..b568708d4f8 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -822,11 +822,11 @@ java.lang.Object runTransaction(com.google.cloud.spanner.connection.Connection$TransactionCallable) - + 7012 com/google/cloud/spanner/SpannerOptions$SpannerEnvironment - com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentials() + com.google.auth.oauth2.GoogleCredentials getDefaultExperimentalHostCredentials() diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java index 171c10c9c92..3080290f720 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java @@ -34,6 +34,7 @@ public class SessionPoolOptions { private static final int DEFAULT_MAX_SESSIONS = 400; private static final int DEFAULT_MIN_SESSIONS = 100; private static final int DEFAULT_INC_STEP = 25; + private static final int EXPERIMENTAL_HOST_REGULAR_SESSIONS = 0; private static final ActionOnExhaustion DEFAULT_ACTION = ActionOnExhaustion.BLOCK; private final int minSessions; private final int maxSessions; @@ -89,8 +90,12 @@ private SessionPoolOptions(Builder builder) { // minSessions > maxSessions is only possible if the user has only set a value for maxSessions. // We allow that to prevent code that only sets a value for maxSessions to break if the // maxSessions value is less than the default for minSessions. - this.minSessions = Math.min(builder.minSessions, builder.maxSessions); - this.maxSessions = builder.maxSessions; + this.minSessions = + builder.isExperimentalHost + ? EXPERIMENTAL_HOST_REGULAR_SESSIONS + : Math.min(builder.minSessions, builder.maxSessions); + this.maxSessions = + builder.isExperimentalHost ? EXPERIMENTAL_HOST_REGULAR_SESSIONS : builder.maxSessions; this.incStep = builder.incStep; this.maxIdleSessions = builder.maxIdleSessions; this.writeSessionsFraction = builder.writeSessionsFraction; @@ -114,26 +119,30 @@ private SessionPoolOptions(Builder builder) { // useMultiplexedSession priority => Environment var > private setter > client default Boolean useMultiplexedSessionFromEnvVariable = getUseMultiplexedSessionFromEnvVariable(); this.useMultiplexedSession = - (useMultiplexedSessionFromEnvVariable != null) - ? useMultiplexedSessionFromEnvVariable - : builder.useMultiplexedSession; + builder.isExperimentalHost + || ((useMultiplexedSessionFromEnvVariable != null) + ? useMultiplexedSessionFromEnvVariable + : builder.useMultiplexedSession); // useMultiplexedSessionForRW priority => Environment var > private setter > client default Boolean useMultiplexedSessionForRWFromEnvVariable = getUseMultiplexedSessionForRWFromEnvVariable(); this.useMultiplexedSessionForRW = - (useMultiplexedSessionForRWFromEnvVariable != null) - ? useMultiplexedSessionForRWFromEnvVariable - : builder.useMultiplexedSessionForRW; + builder.isExperimentalHost + || ((useMultiplexedSessionForRWFromEnvVariable != null) + ? useMultiplexedSessionForRWFromEnvVariable + : builder.useMultiplexedSessionForRW); // useMultiplexedSessionPartitionedOps priority => Environment var > private setter > client // default Boolean useMultiplexedSessionFromEnvVariablePartitionedOps = getUseMultiplexedSessionFromEnvVariablePartitionedOps(); this.useMultiplexedSessionForPartitionedOps = - (useMultiplexedSessionFromEnvVariablePartitionedOps != null) - ? useMultiplexedSessionFromEnvVariablePartitionedOps - : builder.useMultiplexedSessionPartitionedOps; + builder.isExperimentalHost + || ((useMultiplexedSessionFromEnvVariablePartitionedOps != null) + ? useMultiplexedSessionFromEnvVariablePartitionedOps + : builder.useMultiplexedSessionPartitionedOps); this.multiplexedSessionMaintenanceDuration = builder.multiplexedSessionMaintenanceDuration; - this.skipVerifyingBeginTransactionForMuxRW = builder.skipVerifyingBeginTransactionForMuxRW; + this.skipVerifyingBeginTransactionForMuxRW = + builder.isExperimentalHost || builder.skipVerifyingBeginTransactionForMuxRW; } @Override @@ -617,6 +626,7 @@ public static class Builder { private Duration multiplexedSessionMaintenanceDuration = Duration.ofDays(7); private Clock poolMaintainerClock = Clock.INSTANCE; private boolean skipVerifyingBeginTransactionForMuxRW = false; + private boolean isExperimentalHost = false; private static Position getReleaseToPositionFromSystemProperty() { // NOTE: This System property is a beta feature. Support for it can be removed in the future. @@ -813,6 +823,12 @@ public Builder setWarnAndCloseIfInactiveTransactions() { return this; } + @InternalApi + public Builder setExperimentalHost() { + this.isExperimentalHost = true; + return this; + } + /** * If there are inactive transactions, release the resources consumed by such transactions. A * transaction is classified as inactive if it executes for more than a system defined duration. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 43978a1d04c..412bbbb151c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -98,7 +98,6 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -117,10 +116,7 @@ public class SpannerOptions extends ServiceOptions { private static final String API_SHORT_NAME = "Spanner"; private static final String DEFAULT_HOST = "https://spanner.googleapis.com"; - private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\.googleapis\\.com.*"; - - @VisibleForTesting - static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern.compile(CLOUD_SPANNER_HOST_FORMAT); + private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default"; private static final ImmutableSet SCOPES = ImmutableSet.of( @@ -856,13 +852,13 @@ default String getMonitoringHost() { return null; } - default GoogleCredentials getDefaultExternalHostCredentials() { + default GoogleCredentials getDefaultExperimentalHostCredentials() { return null; } } - static final String DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS = - "SPANNER_EXTERNAL_HOST_AUTH_TOKEN"; + static final String DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS = + "SPANNER_EXPERIMENTAL_HOST_AUTH_TOKEN"; /** * Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment @@ -921,8 +917,8 @@ public String getMonitoringHost() { } @Override - public GoogleCredentials getDefaultExternalHostCredentials() { - return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS)); + public GoogleCredentials getDefaultExperimentalHostCredentials() { + return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS)); } } @@ -991,7 +987,7 @@ public static class Builder private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); private SslContext mTLSContext = null; - private boolean isExternalHost = false; + private boolean isExperimentalHost = false; private static String createCustomClientLibToken(String token) { return token + " " + ServiceOptions.getGoogApiClientLibName(); @@ -1484,14 +1480,20 @@ public Builder setDecodeMode(DecodeMode decodeMode) { @Override public Builder setHost(String host) { super.setHost(host); - if (!CLOUD_SPANNER_HOST_PATTERN.matcher(host).matches()) { - this.isExternalHost = true; - } // Setting a host should override any SPANNER_EMULATOR_HOST setting. setEmulatorHost(null); return this; } + @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676") + public Builder setExperimentalHost(String host) { + super.setHost(host); + super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID); + setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build()); + this.isExperimentalHost = true; + return this; + } + /** * Enables gRPC-GCP extension with the default settings. Do not set * GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as @@ -1530,7 +1532,7 @@ public Builder setEmulatorHost(String emulatorHost) { /** * Configures mTLS authentication using the provided client certificate and key files. mTLS is - * only supported for external spanner hosts. + * only supported for experimental spanner hosts. * * @param clientCertificate Path to the client certificate file. * @param clientCertificateKey Path to the client private key file. @@ -1657,8 +1659,8 @@ public SpannerOptions build() { this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); - } else if (isExternalHost && credentials == null) { - credentials = environment.getDefaultExternalHostCredentials(); + } else if (isExperimentalHost && credentials == null) { + credentials = environment.getDefaultExperimentalHostCredentials(); } if (this.numChannels == null) { this.numChannels = @@ -1700,8 +1702,8 @@ public static void useDefaultEnvironment() { } @InternalApi - public static GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv() { - return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS)); + public static GoogleCredentials getDefaultExperimentalCredentialsFromSysEnv() { + return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS)); } private static @Nullable GoogleCredentials getOAuthTokenFromFile(@Nullable String file) { 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 b766545a799..1353a32a6a7 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 @@ -32,6 +32,7 @@ import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS; import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT; +import static com.google.cloud.spanner.connection.ConnectionProperties.IS_EXPERIMENTAL_HOST; import static com.google.cloud.spanner.connection.ConnectionProperties.LENIENT; import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_COMMIT_DELAY; import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_PARTITIONED_PARALLELISM; @@ -221,6 +222,7 @@ public String[] getValidValues() { private static final LocalConnectionChecker LOCAL_CONNECTION_CHECKER = new LocalConnectionChecker(); static final boolean DEFAULT_USE_PLAIN_TEXT = false; + static final boolean DEFAULT_IS_EXPERIMENTAL_HOST = false; static final boolean DEFAULT_AUTOCOMMIT = true; static final boolean DEFAULT_READONLY = false; static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true; @@ -260,6 +262,8 @@ public String[] getValidValues() { static final boolean DEFAULT_AUTO_BATCH_DML = false; static final long DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT = 1L; static final boolean DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION = true; + private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default"; + private static final String DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID = "default"; private static final String PLAIN_TEXT_PROTOCOL = "http:"; private static final String HOST_PROTOCOL = "https:"; @@ -268,6 +272,8 @@ public String[] getValidValues() { private static final String DEFAULT_EMULATOR_HOST = "http://localhost:9010"; /** Use plain text is only for local testing purposes. */ static final String USE_PLAIN_TEXT_PROPERTY_NAME = "usePlainText"; + /** Connect to a Experimental Host * */ + static final String IS_EXPERIMENTAL_HOST_PROPERTY_NAME = "isExperimentalHost"; /** Client certificate path to establish mTLS */ static final String CLIENT_CERTIFICATE_PROPERTY_NAME = "clientCertificate"; /** Client key path to establish mTLS */ @@ -444,6 +450,10 @@ static boolean isEnableTransactionalConnectionStateForPostgreSQL() { USE_PLAIN_TEXT_PROPERTY_NAME, "Use a plain text communication channel (i.e. non-TLS) for communicating with the server (true/false). Set this value to true for communication with the Cloud Spanner emulator.", DEFAULT_USE_PLAIN_TEXT), + ConnectionProperty.createBooleanProperty( + IS_EXPERIMENTAL_HOST_PROPERTY_NAME, + "Set this value to true for communication with an Experimental Host.", + DEFAULT_IS_EXPERIMENTAL_HOST), ConnectionProperty.createStringProperty( CLIENT_CERTIFICATE_PROPERTY_NAME, "Specifies the file path to the client certificate required for establishing an mTLS connection."), @@ -664,7 +674,7 @@ private boolean isValidUri(String uri) { return SPANNER_URI_PATTERN.matcher(uri).matches(); } - private boolean isValidExternalHostUri(String uri) { + private boolean isValidExperimentalHostUri(String uri) { return EXTERNAL_HOST_PATTERN.matcher(uri).matches(); } @@ -725,7 +735,7 @@ private boolean isValidExternalHostUri(String uri) { * @return this builder */ public Builder setUri(String uri) { - if (!isValidExternalHostUri(uri)) { + if (!isValidExperimentalHostUri(uri)) { Preconditions.checkArgument( isValidUri(uri), "The specified URI is not a valid Cloud Spanner connection URI. Please specify a URI in the format \"cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\\?property-name=property-value[;property-name=property-value]*]?\""); @@ -857,10 +867,10 @@ public static Builder newBuilder() { private ConnectionOptions(Builder builder) { Matcher matcher; - boolean isExternalHost = false; - if (builder.isValidExternalHostUri(builder.uri)) { + boolean isExperimentalHostPattern = false; + if (builder.isValidExperimentalHostUri(builder.uri)) { matcher = Builder.EXTERNAL_HOST_PATTERN.matcher(builder.uri); - isExternalHost = true; + isExperimentalHostPattern = true; } else { matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri); } @@ -923,8 +933,8 @@ private ConnectionOptions(Builder builder) { getInitialConnectionPropertyValue(AUTO_CONFIG_EMULATOR), usePlainText, System.getenv()); - GoogleCredentials defaultExternalHostCredentials = - SpannerOptions.getDefaultExternalHostCredentialsFromSysEnv(); + GoogleCredentials defaultExperimentalHostCredentials = + SpannerOptions.getDefaultExperimentalCredentialsFromSysEnv(); // Using credentials on a plain text connection is not allowed, so if the user has not specified // any credentials and is using a plain text connection, we should not try to get the // credentials from the environment, but default to NoCredentials. @@ -939,8 +949,9 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null this.credentials = new GoogleCredentials( new AccessToken(getInitialConnectionPropertyValue(OAUTH_TOKEN), null)); - } else if (isExternalHost && defaultExternalHostCredentials != null) { - this.credentials = defaultExternalHostCredentials; + } else if ((isExperimentalHostPattern || isExperimentalHost()) + && defaultExperimentalHostCredentials != null) { + this.credentials = defaultExperimentalHostCredentials; } else if (getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) != null) { try { this.credentials = getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER).getCredentials(); @@ -981,16 +992,19 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null this.sessionPoolOptions = sessionPoolOptionsBuilder.build(); } else if (builder.sessionPoolOptions != null) { this.sessionPoolOptions = builder.sessionPoolOptions; + } else if (isExperimentalHostPattern || isExperimentalHost()) { + this.sessionPoolOptions = + SessionPoolOptions.newBuilder().setExperimentalHost().setAutoDetectDialect(true).build(); } else { this.sessionPoolOptions = SessionPoolOptions.newBuilder().setAutoDetectDialect(true).build(); } - String projectId = "default"; + String projectId = EXPERIMENTAL_HOST_PROJECT_ID; String instanceId = matcher.group(Builder.INSTANCE_GROUP); - if (!isExternalHost) { + if (!isExperimentalHost() && !isExperimentalHostPattern) { projectId = matcher.group(Builder.PROJECT_GROUP); - } else if (instanceId == null) { - instanceId = "default"; + } else if (instanceId == null && isExperimentalHost()) { + instanceId = DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID; } if (Builder.DEFAULT_PROJECT_ID_PLACEHOLDER.equalsIgnoreCase(projectId)) { projectId = getDefaultProjectId(this.credentials); @@ -1311,6 +1325,10 @@ boolean isUsePlainText() { || getInitialConnectionPropertyValue(USE_PLAIN_TEXT); } + boolean isExperimentalHost() { + return getInitialConnectionPropertyValue(IS_EXPERIMENTAL_HOST); + } + String getClientCertificate() { return getInitialConnectionPropertyValue(CLIENT_CERTIFICATE); } 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 f65dc533570..d9f2d5562b8 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 @@ -47,6 +47,7 @@ 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; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_IS_EXPERIMENTAL_HOST; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_KEEP_TRANSACTION_ALIVE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_LENIENT; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_MAX_PARTITIONED_PARALLELISM; @@ -76,6 +77,7 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.IS_EXPERIMENTAL_HOST_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.KEEP_TRANSACTION_ALIVE_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.LENIENT_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.MAX_PARTITIONED_PARALLELISM_PROPERTY_NAME; @@ -197,7 +199,14 @@ public class ConnectionProperties { BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); - + static final ConnectionProperty IS_EXPERIMENTAL_HOST = + create( + IS_EXPERIMENTAL_HOST_PROPERTY_NAME, + "Set this value to true for communication with a Experimental Host.", + DEFAULT_IS_EXPERIMENTAL_HOST, + BOOLEANS, + BooleanConverter.INSTANCE, + Context.STARTUP); static final ConnectionProperty CLIENT_CERTIFICATE = create( CLIENT_CERTIFICATE_PROPERTY_NAME, diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ExperimentalHostMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ExperimentalHostMockServerTest.java new file mode 100644 index 00000000000..423c3337ab8 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ExperimentalHostMockServerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static org.junit.Assert.assertFalse; + +import com.google.cloud.NoCredentials; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.protobuf.ListValue; +import com.google.protobuf.Value; +import com.google.spanner.v1.BatchCreateSessionsRequest; +import com.google.spanner.v1.ResultSetMetadata; +import com.google.spanner.v1.StructType; +import com.google.spanner.v1.TypeCode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ExperimentalHostMockServerTest extends AbstractMockServerTest { + + private static final String SQL_QUERY = "SELECT * FROM Singers"; + + private static final ResultSetMetadata SINGERS_METADATA = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + StructType.Field.newBuilder() + .setName("FirstName") + .setType( + com.google.spanner.v1.Type.newBuilder().setCode(TypeCode.STRING))) + .addFields( + StructType.Field.newBuilder() + .setName("LastName") + .setType( + com.google.spanner.v1.Type.newBuilder().setCode(TypeCode.STRING))) + .build()) + .build(); + + private static final com.google.spanner.v1.ResultSet SINGERS_RESULT_SET = + com.google.spanner.v1.ResultSet.newBuilder() + .setMetadata(SINGERS_METADATA) + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("Jane")) + .addValues(Value.newBuilder().setStringValue("Doe")) + .build()) + .build(); + + @Test + public void testExperimentalHostPreventsBatchCreateSessions() { + mockSpanner.putStatementResult( + StatementResult.query(Statement.of(SQL_QUERY), SINGERS_RESULT_SET)); + + SpannerOptions options = + SpannerOptions.newBuilder() + .setProjectId("p") + .setCredentials(NoCredentials.getInstance()) + .setExperimentalHost(null) + .setChannelProvider(channelProvider) + .build(); + + try (Spanner spanner = options.getService()) { + DatabaseClient dbClient = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + + // Perform an operation to trigger session creation + ResultSet resultSet = dbClient.singleUse().executeQuery(Statement.of(SQL_QUERY)); + while (resultSet.next()) {} + + assertFalse(mockSpanner.getRequestTypes().contains(BatchCreateSessionsRequest.class)); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 72bbdf82eae..285097d4af7 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -16,7 +16,6 @@ package com.google.cloud.spanner; -import static com.google.cloud.spanner.SpannerOptions.CLOUD_SPANNER_HOST_PATTERN; import static com.google.common.truth.Truth.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -1167,10 +1166,17 @@ public void checkGlobalOpenTelemetryWhenNotInjected() { } @Test - public void testCloudSpannerHostPattern() { - assertTrue(CLOUD_SPANNER_HOST_PATTERN.matcher("https://spanner.googleapis.com").matches()); - assertTrue( - CLOUD_SPANNER_HOST_PATTERN.matcher("https://product-area.googleapis.com:443").matches()); - assertFalse(CLOUD_SPANNER_HOST_PATTERN.matcher("https://some-company.com:443").matches()); + public void testExperimentalHostOptions() { + SpannerOptions options = + SpannerOptions.newBuilder() + .setExperimentalHost("localhost:8080") + .setCredentials(NoCredentials.getInstance()) + .build(); + assertEquals("default", options.getProjectId()); + assertEquals(0, options.getSessionPoolOptions().getMinSessions()); + assertEquals(0, options.getSessionPoolOptions().getMaxSessions()); + assertTrue(options.getSessionPoolOptions().getUseMultiplexedSession()); + assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionForRW()); + assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionPartitionedOps()); } } 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 76771ffcc9b..36ca4c124f5 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 @@ -1270,4 +1270,39 @@ public void testBuildWithValidURIWithPrefixSpanner() { assertThat(options.isAutocommit()).isEqualTo(false); assertThat(options.isReadOnly()).isEqualTo(true); } + + @Test + public void testExperimentalHost() { + ConnectionOptions.Builder builderWithoutExperimentalHostParam = ConnectionOptions.newBuilder(); + builderWithoutExperimentalHostParam.setUri( + "spanner://localhost:15000/instances/default/databases/singers-db;usePlainText=true"); + ConnectionOptions optionsWithoutExperimentalHostParam = + builderWithoutExperimentalHostParam.build(); + assertFalse(optionsWithoutExperimentalHostParam.isExperimentalHost()); + assertEquals(0, optionsWithoutExperimentalHostParam.getSessionPoolOptions().getMinSessions()); + assertTrue( + optionsWithoutExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSession()); + assertTrue( + optionsWithoutExperimentalHostParam + .getSessionPoolOptions() + .getUseMultiplexedSessionForRW()); + assertTrue( + optionsWithoutExperimentalHostParam + .getSessionPoolOptions() + .getUseMultiplexedSessionPartitionedOps()); + + ConnectionOptions.Builder builderWithExperimentalHostParam = ConnectionOptions.newBuilder(); + builderWithExperimentalHostParam.setUri( + "spanner://localhost:15000/projects/default/instances/default/databases/singers-db;usePlainText=true;isExperimentalHost=true"); + ConnectionOptions optionsWithExperimentalHostParam = builderWithExperimentalHostParam.build(); + assertTrue(optionsWithExperimentalHostParam.isExperimentalHost()); + assertEquals(0, optionsWithExperimentalHostParam.getSessionPoolOptions().getMinSessions()); + assertTrue(optionsWithExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSession()); + assertTrue( + optionsWithExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSessionForRW()); + assertTrue( + optionsWithExperimentalHostParam + .getSessionPoolOptions() + .getUseMultiplexedSessionPartitionedOps()); + } }