diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
index b16f5212010..9a71e7679c2 100644
--- a/.github/sync-repo-settings.yaml
+++ b/.github/sync-repo-settings.yaml
@@ -14,6 +14,7 @@ branchProtectionRules:
- units (8)
- units (11)
- 'Kokoro - Test: Integration'
+ - 'Kokoro - Test: Integration with Multiplexed Sessions'
- cla/google
- checkstyle
- compile (8)
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index 1d7e28014c4..f6594602a26 100644
--- a/.github/workflows/unmanaged_dependency_check.yaml
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -17,6 +17,6 @@ jobs:
# repository
.kokoro/build.sh
- name: Unmanaged dependency check
- uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.29.0
+ uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.30.0
with:
bom-path: google-cloud-spanner-bom/pom.xml
diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg
index 326361c6b5e..b20ec8ff352 100644
--- a/.kokoro/presubmit/graalvm-native-17.cfg
+++ b/.kokoro/presubmit/graalvm-native-17.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.29.0"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.30.0"
}
env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index 1b1d4c4bfe3..aad0db97859 100644
--- a/.kokoro/presubmit/graalvm-native.cfg
+++ b/.kokoro/presubmit/graalvm-native.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.29.0"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.30.0"
}
env_vars: {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f3fc6d1395..a878fe0500f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## [6.66.0](https://github.com/googleapis/java-spanner/compare/v6.65.1...v6.66.0) (2024-05-03)
+
+
+### Features
+
+* Allow DDL with autocommit=false ([#3057](https://github.com/googleapis/java-spanner/issues/3057)) ([22833ac](https://github.com/googleapis/java-spanner/commit/22833acf9f073271ce0ee10f2b496f3a1d39566a))
+* Include stack trace of checked out sessions in exception ([#3092](https://github.com/googleapis/java-spanner/issues/3092)) ([ba6a0f6](https://github.com/googleapis/java-spanner/commit/ba6a0f644b6caa4d2f3aa130c6061341b70957dd))
+
+
+### Bug Fixes
+
+* Multiplexed session metrics were not included in refactor move ([#3088](https://github.com/googleapis/java-spanner/issues/3088)) ([f3589c4](https://github.com/googleapis/java-spanner/commit/f3589c430b0e84933a91008bb306c26089788357))
+
+
+### Dependencies
+
+* Update dependency com.google.cloud:sdk-platform-java-config to v3.30.0 ([#3082](https://github.com/googleapis/java-spanner/issues/3082)) ([ddfc98e](https://github.com/googleapis/java-spanner/commit/ddfc98e240fb47ef51075ba4461bf9a98aa25ce0))
+
## [6.65.1](https://github.com/googleapis/java-spanner/compare/v6.65.0...v6.65.1) (2024-04-30)
diff --git a/README.md b/README.md
index 2f904b1c112..5333b8b4bda 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ If you are using Maven without the BOM, add this to your dependencies:
com.google.cloud
google-cloud-spanner
- 6.65.0
+ 6.65.1
```
@@ -50,20 +50,20 @@ If you are using Maven without the BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:
```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.37.0')
+implementation platform('com.google.cloud:libraries-bom:26.38.0')
implementation 'com.google.cloud:google-cloud-spanner'
```
If you are using Gradle without BOM, add this to your dependencies:
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner:6.65.0'
+implementation 'com.google.cloud:google-cloud-spanner:6.65.1'
```
If you are using SBT, add this to your dependencies:
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.65.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.65.1"
```
@@ -651,7 +651,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
[stability-image]: https://img.shields.io/badge/stability-stable-green
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
-[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.65.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.65.1
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index cd672b3fc54..bcd6137641f 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -24,7 +24,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
@@ -92,7 +92,7 @@
com.google.cloud
google-cloud-spanner
- 6.63.0
+ 6.65.1
commons-cli
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index b3778261e43..f73943c2924 100644
--- a/google-cloud-spanner-bom/pom.xml
+++ b/google-cloud-spanner-bom/pom.xml
@@ -3,12 +3,12 @@
4.0.0
com.google.cloud
google-cloud-spanner-bom
- 6.65.1
+ 6.66.0
pom
com.google.cloud
sdk-platform-java-config
- 3.29.0
+ 3.30.0
Google Cloud Spanner BOM
@@ -53,43 +53,43 @@
com.google.cloud
google-cloud-spanner
- 6.65.1
+ 6.66.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml
index eb3d34a63d8..21239775cee 100644
--- a/google-cloud-spanner-executor/pom.xml
+++ b/google-cloud-spanner-executor/pom.xml
@@ -5,14 +5,14 @@
4.0.0
com.google.cloud
google-cloud-spanner-executor
- 6.65.1
+ 6.66.0
jar
Google Cloud Spanner Executor
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index 5fea47fe9a8..92dcab6e2ce 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -656,5 +656,17 @@
com/google/cloud/spanner/connection/Connection
com.google.cloud.spanner.Spanner getSpanner()
+
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ void setDdlInTransactionMode(com.google.cloud.spanner.connection.DdlInTransactionMode)
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ com.google.cloud.spanner.connection.DdlInTransactionMode getDdlInTransactionMode()
+
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 2c0480522f8..12f15f1eb31 100644
--- a/google-cloud-spanner/pom.xml
+++ b/google-cloud-spanner/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-spanner
- 6.65.1
+ 6.66.0
jar
Google Cloud Spanner
https://github.com/googleapis/java-spanner
@@ -11,7 +11,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
google-cloud-spanner
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java
new file mode 100644
index 00000000000..92035a18418
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 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 com.google.api.gax.rpc.ServerStream;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Options.TransactionOption;
+import com.google.cloud.spanner.Options.UpdateOption;
+import com.google.spanner.v1.BatchWriteResponse;
+
+/**
+ * Base class for the Multiplexed Session {@link DatabaseClient} implementation. Throws {@link
+ * UnsupportedOperationException} for all methods that are currently not supported for multiplexed
+ * sessions. The concrete implementation implements the methods that are supported with multiplexed
+ * sessions.
+ */
+abstract class AbstractMultiplexedSessionDatabaseClient implements DatabaseClient {
+
+ @Override
+ public Dialect getDialect() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDatabaseRole() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Timestamp write(Iterable mutations) throws SpannerException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CommitResponse writeWithOptions(Iterable mutations, TransactionOption... options)
+ throws SpannerException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CommitResponse writeAtLeastOnceWithOptions(
+ Iterable mutations, TransactionOption... options) throws SpannerException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServerStream batchWriteAtLeastOnce(
+ Iterable mutationGroups, TransactionOption... options)
+ throws SpannerException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public TransactionRunner readWriteTransaction(TransactionOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public TransactionManager transactionManager(TransactionOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AsyncRunner runAsync(TransactionOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long executePartitionedUpdate(Statement stmt, UpdateOption... options) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
index 18a1cc3692c..4d17ba4e1b7 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
@@ -809,6 +809,7 @@ public final void invalidate() {
@Override
public void close() {
+ session.onTransactionDone();
span.end();
synchronized (lock) {
isClosed = true;
@@ -844,11 +845,14 @@ public void onTransactionMetadata(Transaction transaction, boolean shouldInclude
@Override
public SpannerException onError(SpannerException e, boolean withBeginTransaction) {
+ this.session.onError(e);
return e;
}
@Override
- public void onDone(boolean withBeginTransaction) {}
+ public void onDone(boolean withBeginTransaction) {
+ this.session.onReadDone();
+ }
private ResultSet readInternal(
String table,
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
index c89cce9a79e..b2d6b19a528 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
@@ -21,7 +21,6 @@
import com.google.cloud.spanner.Options.TransactionOption;
import com.google.cloud.spanner.Options.UpdateOption;
import com.google.cloud.spanner.SessionPool.PooledSessionFuture;
-import com.google.cloud.spanner.SessionPool.SessionFutureWrapper;
import com.google.cloud.spanner.SpannerImpl.ClosedException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
@@ -36,15 +35,26 @@ class DatabaseClientImpl implements DatabaseClient {
private final TraceWrapper tracer;
@VisibleForTesting final String clientId;
@VisibleForTesting final SessionPool pool;
+ @VisibleForTesting final MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient;
@VisibleForTesting
DatabaseClientImpl(SessionPool pool, TraceWrapper tracer) {
- this("", pool, tracer);
+ this("", pool, /* multiplexedSessionDatabaseClient = */ null, tracer);
}
+ @VisibleForTesting
DatabaseClientImpl(String clientId, SessionPool pool, TraceWrapper tracer) {
+ this(clientId, pool, /* multiplexedSessionDatabaseClient = */ null, tracer);
+ }
+
+ DatabaseClientImpl(
+ String clientId,
+ SessionPool pool,
+ @Nullable MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient,
+ TraceWrapper tracer) {
this.clientId = clientId;
this.pool = pool;
+ this.multiplexedSessionDatabaseClient = multiplexedSessionDatabaseClient;
this.tracer = tracer;
}
@@ -54,7 +64,11 @@ PooledSessionFuture getSession() {
}
@VisibleForTesting
- SessionFutureWrapper getMultiplexedSession() {
+ DatabaseClient getMultiplexedSession() {
+ if (this.multiplexedSessionDatabaseClient != null
+ && this.multiplexedSessionDatabaseClient.isMultiplexedSessionsSupported()) {
+ return this.multiplexedSessionDatabaseClient;
+ }
return pool.getMultiplexedSessionWithFallback();
}
@@ -270,7 +284,18 @@ private T runWithSessionRetry(Function callable) {
}
}
+ boolean isValid() {
+ return pool.isValid()
+ && (multiplexedSessionDatabaseClient == null
+ || multiplexedSessionDatabaseClient.isValid()
+ || !multiplexedSessionDatabaseClient.isMultiplexedSessionsSupported());
+ }
+
ListenableFuture closeAsync(ClosedException closedException) {
+ if (this.multiplexedSessionDatabaseClient != null) {
+ // This method is non-blocking.
+ this.multiplexedSessionDatabaseClient.close();
+ }
return pool.closeAsync(closedException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java
new file mode 100644
index 00000000000..928927d49a0
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2024 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 com.google.cloud.spanner.SessionImpl.NO_CHANNEL_HINT;
+
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.spanner.DelayedReadContext.DelayedReadOnlyTransaction;
+import com.google.cloud.spanner.MultiplexedSessionDatabaseClient.MultiplexedSessionTransaction;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * Represents a delayed execution of a transaction on a multiplexed session. The execution is
+ * delayed because the multiplexed session is not yet ready. This class is only used during client
+ * creation before the multiplexed session has been created. The use of this class while the
+ * multiplexed session is still being created ensures that the creation of a {@link DatabaseClient}
+ * is non-blocking.
+ */
+class DelayedMultiplexedSessionTransaction extends AbstractMultiplexedSessionDatabaseClient {
+
+ private final MultiplexedSessionDatabaseClient client;
+
+ private final ISpan span;
+
+ private final ApiFuture sessionFuture;
+
+ DelayedMultiplexedSessionTransaction(
+ MultiplexedSessionDatabaseClient client,
+ ISpan span,
+ ApiFuture sessionFuture) {
+ this.client = client;
+ this.span = span;
+ this.sessionFuture = sessionFuture;
+ }
+
+ @Override
+ public ReadContext singleUse() {
+ return new DelayedReadContext<>(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, true)
+ .singleUse(),
+ MoreExecutors.directExecutor()));
+ }
+
+ @Override
+ public ReadContext singleUse(TimestampBound bound) {
+ return new DelayedReadContext<>(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, true)
+ .singleUse(bound),
+ MoreExecutors.directExecutor()));
+ }
+
+ @Override
+ public ReadOnlyTransaction singleUseReadOnlyTransaction() {
+ return new DelayedReadOnlyTransaction(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, true)
+ .singleUseReadOnlyTransaction(),
+ MoreExecutors.directExecutor()));
+ }
+
+ @Override
+ public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) {
+ return new DelayedReadOnlyTransaction(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, true)
+ .singleUseReadOnlyTransaction(bound),
+ MoreExecutors.directExecutor()));
+ }
+
+ @Override
+ public ReadOnlyTransaction readOnlyTransaction() {
+ return new DelayedReadOnlyTransaction(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, false)
+ .readOnlyTransaction(),
+ MoreExecutors.directExecutor()));
+ }
+
+ @Override
+ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) {
+ return new DelayedReadOnlyTransaction(
+ ApiFutures.transform(
+ this.sessionFuture,
+ sessionReference ->
+ new MultiplexedSessionTransaction(
+ client, span, sessionReference, NO_CHANNEL_HINT, false)
+ .readOnlyTransaction(bound),
+ MoreExecutors.directExecutor()));
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java
new file mode 100644
index 00000000000..752c41cdea3
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2024 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 com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Options.QueryOption;
+import com.google.cloud.spanner.Options.ReadOption;
+import com.google.common.base.Suppliers;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+/**
+ * Represents a {@link ReadContext} using a multiplexed session that is not yet ready. The execution
+ * will be delayed until the multiplexed session has been created and is ready. This class is only
+ * used during the startup of the client and the multiplexed session has not yet been created. This
+ * ensures that the creation of {@link DatabaseClient} is non-blocking.
+ */
+class DelayedReadContext implements ReadContext {
+
+ private final ApiFuture readContextFuture;
+
+ DelayedReadContext(ApiFuture readContextFuture) {
+ this.readContextFuture = readContextFuture;
+ }
+
+ T getReadContext() {
+ try {
+ return this.readContextFuture.get();
+ } catch (ExecutionException executionException) {
+ throw SpannerExceptionFactory.asSpannerException(executionException.getCause());
+ } catch (InterruptedException interruptedException) {
+ throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
+ }
+ }
+
+ @Override
+ public ResultSet read(
+ String table, KeySet keys, Iterable columns, ReadOption... options) {
+ return new ForwardingResultSet(
+ Suppliers.memoize(() -> getReadContext().read(table, keys, columns, options)));
+ }
+
+ @Override
+ public AsyncResultSet readAsync(
+ String table, KeySet keys, Iterable columns, ReadOption... options) {
+ return new ForwardingAsyncResultSet(
+ Suppliers.memoize(() -> getReadContext().readAsync(table, keys, columns, options)));
+ }
+
+ @Override
+ public ResultSet readUsingIndex(
+ String table, String index, KeySet keys, Iterable columns, ReadOption... options) {
+ return new ForwardingResultSet(
+ Suppliers.memoize(
+ () -> getReadContext().readUsingIndex(table, index, keys, columns, options)));
+ }
+
+ @Override
+ public AsyncResultSet readUsingIndexAsync(
+ String table, String index, KeySet keys, Iterable columns, ReadOption... options) {
+ return new ForwardingAsyncResultSet(
+ Suppliers.memoize(
+ () -> getReadContext().readUsingIndexAsync(table, index, keys, columns, options)));
+ }
+
+ @Nullable
+ @Override
+ public Struct readRow(String table, Key key, Iterable columns) {
+ // This is allowed to be blocking.
+ return getReadContext().readRow(table, key, columns);
+ }
+
+ @Override
+ public ApiFuture readRowAsync(String table, Key key, Iterable columns) {
+ return ApiFutures.transformAsync(
+ this.readContextFuture,
+ readContext -> readContext.readRowAsync(table, key, columns),
+ MoreExecutors.directExecutor());
+ }
+
+ @Nullable
+ @Override
+ public Struct readRowUsingIndex(String table, String index, Key key, Iterable columns) {
+ // This is allowed to be blocking.
+ return getReadContext().readRowUsingIndex(table, index, key, columns);
+ }
+
+ @Override
+ public ApiFuture readRowUsingIndexAsync(
+ String table, String index, Key key, Iterable columns) {
+ return ApiFutures.transformAsync(
+ this.readContextFuture,
+ readContext -> readContext.readRowUsingIndexAsync(table, index, key, columns),
+ MoreExecutors.directExecutor());
+ }
+
+ @Override
+ public ResultSet executeQuery(Statement statement, QueryOption... options) {
+ return new ForwardingResultSet(
+ Suppliers.memoize(() -> getReadContext().executeQuery(statement, options)));
+ }
+
+ @Override
+ public AsyncResultSet executeQueryAsync(Statement statement, QueryOption... options) {
+ return new ForwardingAsyncResultSet(
+ Suppliers.memoize(() -> getReadContext().executeQueryAsync(statement, options)));
+ }
+
+ @Override
+ public ResultSet analyzeQuery(Statement statement, QueryAnalyzeMode queryMode) {
+ return new ForwardingResultSet(
+ Suppliers.memoize(() -> getReadContext().analyzeQuery(statement, queryMode)));
+ }
+
+ @Override
+ public void close() {
+ try {
+ this.readContextFuture.get().close();
+ } catch (Throwable ignore) {
+ // Ignore any errors during close, as this error has already propagated to the user through
+ // other means.
+ }
+ }
+
+ /**
+ * Represents a {@link ReadContext} using a multiplexed session that is not yet ready. The
+ * execution will be delayed until the multiplexed session has been created and is ready.
+ */
+ static class DelayedReadOnlyTransaction extends DelayedReadContext
+ implements ReadOnlyTransaction {
+ DelayedReadOnlyTransaction(ApiFuture readContextFuture) {
+ super(readContextFuture);
+ }
+
+ @Override
+ public Timestamp getReadTimestamp() {
+ return getReadContext().getReadTimestamp();
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingAsyncResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingAsyncResultSet.java
index e94590bd092..a0197cb7b8d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingAsyncResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingAsyncResultSet.java
@@ -19,46 +19,54 @@
import com.google.api.core.ApiFuture;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
import java.util.List;
import java.util.concurrent.Executor;
/** Forwarding implementation of {@link AsyncResultSet} that forwards all calls to a delegate. */
public class ForwardingAsyncResultSet extends ForwardingResultSet implements AsyncResultSet {
- final AsyncResultSet delegate;
public ForwardingAsyncResultSet(AsyncResultSet delegate) {
super(Preconditions.checkNotNull(delegate));
- this.delegate = delegate;
+ }
+
+ ForwardingAsyncResultSet(Supplier delegateSupplier) {
+ super(Preconditions.checkNotNull(delegateSupplier));
+ }
+
+ @Override
+ AsyncResultSet getDelegate() {
+ return (AsyncResultSet) super.getDelegate();
}
@Override
public CursorState tryNext() throws SpannerException {
- return delegate.tryNext();
+ return getDelegate().tryNext();
}
@Override
public ApiFuture setCallback(Executor exec, ReadyCallback cb) {
- return delegate.setCallback(exec, cb);
+ return getDelegate().setCallback(exec, cb);
}
@Override
public void cancel() {
- delegate.cancel();
+ getDelegate().cancel();
}
@Override
public void resume() {
- delegate.resume();
+ getDelegate().resume();
}
@Override
public ApiFuture> toListAsync(
Function transformer, Executor executor) {
- return delegate.toListAsync(transformer, executor);
+ return getDelegate().toListAsync(transformer, executor);
}
@Override
public List toList(Function transformer) throws SpannerException {
- return delegate.toList(transformer);
+ return getDelegate().toList(transformer);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingResultSet.java
index 18ecbeceb0f..babbb310a45 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingResultSet.java
@@ -25,14 +25,14 @@
/** Forwarding implementation of ResultSet that forwards all calls to a delegate. */
public class ForwardingResultSet extends ForwardingStructReader implements ProtobufResultSet {
- private Supplier delegate;
+ private Supplier extends ResultSet> delegate;
public ForwardingResultSet(ResultSet delegate) {
super(delegate);
this.delegate = Suppliers.ofInstance(Preconditions.checkNotNull(delegate));
}
- public ForwardingResultSet(Supplier supplier) {
+ public ForwardingResultSet(Supplier extends ResultSet> supplier) {
super(supplier);
this.delegate = supplier;
}
@@ -50,6 +50,10 @@ void replaceDelegate(ResultSet newDelegate) {
this.delegate = Suppliers.ofInstance(Preconditions.checkNotNull(newDelegate));
}
+ ResultSet getDelegate() {
+ return delegate.get();
+ }
+
@Override
public boolean next() throws SpannerException {
return delegate.get().next();
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
index 37a4792ad87..7b61901a60e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
@@ -90,6 +90,8 @@ public boolean next() throws SpannerException {
boolean hasNext = currRow.consumeRow(iterator);
if (!hasNext) {
statistics = iterator.getStats();
+ // Close the ResultSet when there is no more data.
+ close();
}
return hasNext;
} catch (Throwable t) {
@@ -113,9 +115,14 @@ public ResultSetMetadata getMetadata() {
@Override
public void close() {
+ synchronized (this) {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ }
listener.onDone(iterator.isWithBeginTransaction());
iterator.close("ResultSet closed");
- closed = true;
}
@Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java
new file mode 100644
index 00000000000..e742481be2c
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2024 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 com.google.cloud.spanner.SessionImpl.NO_CHANNEL_HINT;
+
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.api.core.SettableApiFuture;
+import com.google.cloud.spanner.SessionClient.SessionConsumer;
+import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * {@link DatabaseClient} implementation that uses a single multiplexed session to execute
+ * transactions.
+ */
+final class MultiplexedSessionDatabaseClient extends AbstractMultiplexedSessionDatabaseClient {
+
+ /**
+ * Represents a single transaction on a multiplexed session. This can be both a single-use or
+ * multi-use transaction, and both read/write or read-only transaction. This can be compared to a
+ * 'checked out session' of a pool, except as multiplexed sessions support multiple parallel
+ * transactions, we do not need to actually check out and exclusively reserve a single session for
+ * a transaction. This class therefore only contains context information about the current
+ * transaction, such as the current span, and a reference to the multiplexed session that is used
+ * for the transaction.
+ */
+ static class MultiplexedSessionTransaction extends SessionImpl {
+ private final MultiplexedSessionDatabaseClient client;
+
+ private final boolean singleUse;
+
+ private final int singleUseChannelHint;
+
+ private boolean done;
+
+ MultiplexedSessionTransaction(
+ MultiplexedSessionDatabaseClient client,
+ ISpan span,
+ SessionReference sessionReference,
+ int singleUseChannelHint,
+ boolean singleUse) {
+ super(client.sessionClient.getSpanner(), sessionReference, singleUseChannelHint);
+ this.client = client;
+ this.singleUse = singleUse;
+ this.singleUseChannelHint = singleUseChannelHint;
+ this.client.numSessionsAcquired.incrementAndGet();
+ setCurrentSpan(span);
+ }
+
+ @Override
+ void onError(SpannerException spannerException) {
+ if (this.client.resourceNotFoundException.get() == null
+ && (spannerException instanceof DatabaseNotFoundException
+ || spannerException instanceof InstanceNotFoundException
+ || spannerException instanceof SessionNotFoundException)) {
+ // This could in theory set this field more than once, but we don't want to bother with
+ // synchronizing, as it does not really matter exactly which error is set.
+ this.client.resourceNotFoundException.set((ResourceNotFoundException) spannerException);
+ }
+ }
+
+ @Override
+ void onReadDone() {
+ // This method is called whenever a ResultSet that was returned by this transaction is closed.
+ // Close the active transaction if this is a single-use transaction. This ensures that the
+ // active span is ended.
+ if (this.singleUse && getActiveTransaction() != null) {
+ getActiveTransaction().close();
+ setActive(null);
+ if (this.singleUseChannelHint != NO_CHANNEL_HINT) {
+ this.client.channelUsage.clear(this.singleUseChannelHint);
+ }
+ this.client.numCurrentSingleUseTransactions.decrementAndGet();
+ }
+ }
+
+ @Override
+ void onTransactionDone() {
+ boolean markedDone = false;
+ synchronized (this) {
+ if (!this.done) {
+ this.done = true;
+ markedDone = true;
+ }
+ }
+ if (markedDone) {
+ client.numSessionsReleased.incrementAndGet();
+ }
+ }
+
+ @Override
+ public void close() {
+ // no-op, we don't want to delete the multiplexed session.
+ }
+ }
+
+ /**
+ * Keeps track of which channels have been 'given' to single-use transactions for a given Spanner
+ * instance.
+ */
+ private static final Map CHANNEL_USAGE = new HashMap<>();
+
+ private final BitSet channelUsage;
+
+ private final int numChannels;
+
+ /**
+ * The number of single-use read-only transactions currently running on this multiplexed session.
+ */
+ private final AtomicInteger numCurrentSingleUseTransactions = new AtomicInteger();
+
+ private boolean isClosed;
+
+ /** The duration before we try to replace the multiplexed session. The default is 7 days. */
+ private final Duration sessionExpirationDuration;
+
+ private final SessionClient sessionClient;
+
+ private final TraceWrapper tracer;
+
+ /** The current multiplexed session that is used by this client. */
+ private final AtomicReference> multiplexedSessionReference;
+
+ /** The expiration date/time of the current multiplexed session. */
+ private final AtomicReference expirationDate;
+
+ /**
+ * The maintainer runs every 10 minutes to check whether the multiplexed session should be
+ * refreshed.
+ */
+ private final MultiplexedSessionMaintainer maintainer;
+
+ /**
+ * If a {@link DatabaseNotFoundException} or {@link InstanceNotFoundException} is returned by the
+ * server, then we set this field to mark the client as invalid.
+ */
+ private final AtomicReference resourceNotFoundException =
+ new AtomicReference<>();
+
+ private final AtomicLong numSessionsAcquired = new AtomicLong();
+
+ private final AtomicLong numSessionsReleased = new AtomicLong();
+
+ /**
+ * This flag is set to true if the server return UNIMPLEMENTED when we try to create a multiplexed
+ * session. TODO: Remove once this is guaranteed to be available.
+ */
+ private final AtomicBoolean unimplemented = new AtomicBoolean(false);
+
+ MultiplexedSessionDatabaseClient(SessionClient sessionClient) {
+ this(sessionClient, Clock.systemUTC());
+ }
+
+ @VisibleForTesting
+ MultiplexedSessionDatabaseClient(SessionClient sessionClient, Clock clock) {
+ this.numChannels = sessionClient.getSpanner().getOptions().getNumChannels();
+ synchronized (CHANNEL_USAGE) {
+ CHANNEL_USAGE.putIfAbsent(sessionClient.getSpanner(), new BitSet(numChannels));
+ this.channelUsage = CHANNEL_USAGE.get(sessionClient.getSpanner());
+ }
+ this.sessionExpirationDuration =
+ Duration.ofMillis(
+ sessionClient
+ .getSpanner()
+ .getOptions()
+ .getSessionPoolOptions()
+ .getMultiplexedSessionMaintenanceDuration()
+ .toMillis());
+ // Initialize the expiration date to the current time + 7 days to avoid unnecessary null checks.
+ // The time difference with the actual creation is small enough that it does not matter.
+ this.expirationDate = new AtomicReference<>(Instant.now().plus(this.sessionExpirationDuration));
+ this.sessionClient = sessionClient;
+ this.maintainer = new MultiplexedSessionMaintainer(clock);
+ this.tracer = sessionClient.getSpanner().getTracer();
+ final SettableApiFuture initialSessionReferenceFuture =
+ SettableApiFuture.create();
+ this.multiplexedSessionReference = new AtomicReference<>(initialSessionReferenceFuture);
+ this.sessionClient.asyncCreateMultiplexedSession(
+ new SessionConsumer() {
+ @Override
+ public void onSessionReady(SessionImpl session) {
+ initialSessionReferenceFuture.set(session.getSessionReference());
+ // only start the maintainer if we actually managed to create a session in the first
+ // place.
+ maintainer.start();
+ }
+
+ @Override
+ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount) {
+ // Mark multiplexes sessions as unimplemented and fall back to regular sessions if
+ // UNIMPLEMENTED is returned.
+ maybeMarkUnimplemented(t);
+ initialSessionReferenceFuture.setException(t);
+ }
+ });
+ maybeWaitForSessionCreation(
+ sessionClient.getSpanner().getOptions().getSessionPoolOptions(),
+ initialSessionReferenceFuture);
+ }
+
+ private static void maybeWaitForSessionCreation(
+ SessionPoolOptions sessionPoolOptions, ApiFuture future) {
+ org.threeten.bp.Duration waitDuration = sessionPoolOptions.getWaitForMinSessions();
+ if (waitDuration != null && !waitDuration.isZero()) {
+ long timeoutMillis = waitDuration.toMillis();
+ try {
+ future.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException executionException) {
+ throw SpannerExceptionFactory.asSpannerException(executionException.getCause());
+ } catch (InterruptedException interruptedException) {
+ throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
+ } catch (TimeoutException timeoutException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.DEADLINE_EXCEEDED,
+ "Timed out after waiting " + timeoutMillis + "ms for multiplexed session creation");
+ }
+ }
+ }
+
+ private void maybeMarkUnimplemented(Throwable t) {
+ SpannerException spannerException = SpannerExceptionFactory.asSpannerException(t);
+ if (spannerException.getErrorCode() == ErrorCode.UNIMPLEMENTED) {
+ unimplemented.set(true);
+ }
+ }
+
+ boolean isValid() {
+ return resourceNotFoundException.get() == null;
+ }
+
+ AtomicLong getNumSessionsAcquired() {
+ return this.numSessionsAcquired;
+ }
+
+ AtomicLong getNumSessionsReleased() {
+ return this.numSessionsReleased;
+ }
+
+ boolean isMultiplexedSessionsSupported() {
+ return !this.unimplemented.get();
+ }
+
+ void close() {
+ synchronized (this) {
+ if (!this.isClosed) {
+ this.isClosed = true;
+ this.maintainer.stop();
+ }
+ }
+ }
+
+ @VisibleForTesting
+ MultiplexedSessionMaintainer getMaintainer() {
+ return this.maintainer;
+ }
+
+ @VisibleForTesting
+ SessionReference getCurrentSessionReference() {
+ try {
+ return this.multiplexedSessionReference.get().get();
+ } catch (ExecutionException executionException) {
+ throw SpannerExceptionFactory.asSpannerException(executionException.getCause());
+ } catch (InterruptedException interruptedException) {
+ throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
+ }
+ }
+
+ /**
+ * Returns true if the multiplexed session has been created. This client can be used before the
+ * session has been created, and will in that case use a delayed transaction that contains a
+ * future reference to the multiplexed session. The delayed transaction will block at the first
+ * actual statement that is being executed (e.g. the first query that is sent to Spanner).
+ */
+ private boolean isMultiplexedSessionCreated() {
+ return multiplexedSessionReference.get().isDone();
+ }
+
+ private DatabaseClient createMultiplexedSessionTransaction(boolean singleUse) {
+ Preconditions.checkState(!isClosed, "This client has been closed");
+ return isMultiplexedSessionCreated()
+ ? createDirectMultiplexedSessionTransaction(singleUse)
+ : createDelayedMultiplexSessionTransaction();
+ }
+
+ private MultiplexedSessionTransaction createDirectMultiplexedSessionTransaction(
+ boolean singleUse) {
+ try {
+ return new MultiplexedSessionTransaction(
+ this,
+ tracer.getCurrentSpan(),
+ // Getting the result of the SettableApiFuture that contains the multiplexed session will
+ // also automatically propagate any error that happened during the creation of the
+ // session, such as for example a DatabaseNotFound exception. We therefore do not need
+ // any special handling of such errors.
+ multiplexedSessionReference.get().get(),
+ singleUse ? getSingleUseChannelHint() : NO_CHANNEL_HINT,
+ singleUse);
+ } catch (ExecutionException executionException) {
+ throw SpannerExceptionFactory.asSpannerException(executionException.getCause());
+ } catch (InterruptedException interruptedException) {
+ throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
+ }
+ }
+
+ private DelayedMultiplexedSessionTransaction createDelayedMultiplexSessionTransaction() {
+ return new DelayedMultiplexedSessionTransaction(
+ this, tracer.getCurrentSpan(), multiplexedSessionReference.get());
+ }
+
+ private int getSingleUseChannelHint() {
+ if (this.numCurrentSingleUseTransactions.incrementAndGet() > this.numChannels) {
+ return NO_CHANNEL_HINT;
+ }
+ synchronized (this.channelUsage) {
+ // Get the first unused channel.
+ int channel = this.channelUsage.nextClearBit(/* fromIndex = */ 0);
+ // BitSet returns an index larger than its original size if all the bits are set.
+ // This then means that all channels have already been assigned to single-use transactions,
+ // and that we should not use a specific channel, but rather pick a random one.
+ if (channel == this.numChannels) {
+ return NO_CHANNEL_HINT;
+ }
+ this.channelUsage.set(channel);
+ return channel;
+ }
+ }
+
+ @Override
+ public ReadContext singleUse() {
+ return createMultiplexedSessionTransaction(true).singleUse();
+ }
+
+ @Override
+ public ReadContext singleUse(TimestampBound bound) {
+ return createMultiplexedSessionTransaction(true).singleUse(bound);
+ }
+
+ @Override
+ public ReadOnlyTransaction singleUseReadOnlyTransaction() {
+ return createMultiplexedSessionTransaction(true).singleUseReadOnlyTransaction();
+ }
+
+ @Override
+ public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) {
+ return createMultiplexedSessionTransaction(true).singleUseReadOnlyTransaction(bound);
+ }
+
+ @Override
+ public ReadOnlyTransaction readOnlyTransaction() {
+ return createMultiplexedSessionTransaction(false).readOnlyTransaction();
+ }
+
+ @Override
+ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) {
+ return createMultiplexedSessionTransaction(false).readOnlyTransaction(bound);
+ }
+
+ /**
+ * It is enough with one executor to maintain the multiplexed sessions in all the clients, as they
+ * do not need to be updated often, and the maintenance task is light.
+ */
+ private static final ScheduledExecutorService MAINTAINER_SERVICE =
+ Executors.newScheduledThreadPool(
+ /* corePoolSize = */ 0,
+ ThreadFactoryUtil.createVirtualOrPlatformDaemonThreadFactory(
+ "multiplexed-session-maintainer", /* tryVirtual = */ false));
+
+ final class MultiplexedSessionMaintainer {
+ private final Clock clock;
+
+ private ScheduledFuture> scheduledFuture;
+
+ MultiplexedSessionMaintainer(Clock clock) {
+ this.clock = clock;
+ }
+
+ void start() {
+ // Schedule the maintainer to run once every ten minutes (by default).
+ long loopFrequencyMillis =
+ MultiplexedSessionDatabaseClient.this
+ .sessionClient
+ .getSpanner()
+ .getOptions()
+ .getSessionPoolOptions()
+ .getMultiplexedSessionMaintenanceLoopFrequency()
+ .toMillis();
+ this.scheduledFuture =
+ MAINTAINER_SERVICE.scheduleAtFixedRate(
+ this::maintain, loopFrequencyMillis, loopFrequencyMillis, TimeUnit.MILLISECONDS);
+ }
+
+ void stop() {
+ if (this.scheduledFuture != null) {
+ this.scheduledFuture.cancel(false);
+ }
+ }
+
+ void maintain() {
+ if (clock.instant().isAfter(expirationDate.get())) {
+ sessionClient.asyncCreateMultiplexedSession(
+ new SessionConsumer() {
+ @Override
+ public void onSessionReady(SessionImpl session) {
+ multiplexedSessionReference.set(
+ ApiFutures.immediateFuture(session.getSessionReference()));
+ expirationDate.set(
+ clock
+ .instant()
+ .plus(MultiplexedSessionDatabaseClient.this.sessionExpirationDuration));
+ }
+
+ @Override
+ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount) {
+ // ignore any errors during re-creation of the multiplexed session. This means that
+ // we continue to use the session that has passed its expiration date for now, and
+ // that a new attempt at creating a new session will be done in 10 minutes from now.
+ // The only exception to this rule is if the server returns UNIMPLEMENTED. In that
+ // case we invalidate the client and fall back to regular sessions.
+ maybeMarkUnimplemented(t);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
index d498bb232a1..949265ea28a 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
@@ -142,6 +142,10 @@ public void invalidate() {
@Override
public void setSpan(ISpan span) {}
+ /** No-op method needed to implement SessionTransaction interface. */
+ @Override
+ public void close() {}
+
private Duration tryUpdateTimeout(final Duration timeout, final Stopwatch stopwatch) {
final Duration remainingTimeout =
timeout.minus(stopwatch.elapsed(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
index 8d7dcb34756..ab985cebf45 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import static com.google.cloud.spanner.SessionClient.optionMap;
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;
import com.google.api.core.ApiFuture;
@@ -27,6 +28,7 @@
import com.google.cloud.spanner.AbstractReadContext.SingleUseReadOnlyTransaction;
import com.google.cloud.spanner.Options.TransactionOption;
import com.google.cloud.spanner.Options.UpdateOption;
+import com.google.cloud.spanner.SessionClient.SessionOption;
import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl;
import com.google.cloud.spanner.spi.v1.SpannerRpc;
import com.google.common.base.Ticker;
@@ -92,19 +94,38 @@ interface SessionTransaction {
/** Registers the current span on the transaction. */
void setSpan(ISpan span);
+
+ /** Closes the transaction. */
+ void close();
}
+ static final int NO_CHANNEL_HINT = -1;
+
private final SpannerImpl spanner;
private final SessionReference sessionReference;
private SessionTransaction activeTransaction;
private ISpan currentSpan;
private final Clock clock;
+ private final Map options;
SessionImpl(SpannerImpl spanner, SessionReference sessionReference) {
+ this(spanner, sessionReference, NO_CHANNEL_HINT);
+ }
+
+ SessionImpl(SpannerImpl spanner, SessionReference sessionReference, int channelHint) {
this.spanner = spanner;
this.tracer = spanner.getTracer();
this.sessionReference = sessionReference;
this.clock = spanner.getOptions().getSessionPoolOptions().getPoolMaintainerClock();
+ this.options = createOptions(sessionReference, channelHint);
+ }
+
+ static Map createOptions(
+ SessionReference sessionReference, int channelHint) {
+ if (channelHint == NO_CHANNEL_HINT) {
+ return sessionReference.getOptions();
+ }
+ return optionMap(SessionOption.channelHint(channelHint));
}
@Override
@@ -113,7 +134,7 @@ public String getName() {
}
Map getOptions() {
- return sessionReference.getOptions();
+ return options;
}
void setCurrentSpan(ISpan span) {
@@ -440,6 +461,10 @@ TransactionContextImpl newTransaction(Options options) {
.build();
}
+ SessionTransaction getActiveTransaction() {
+ return this.activeTransaction;
+ }
+
T setActive(@Nullable T ctx) {
throwIfTransactionsPending();
// multiplexed sessions support running concurrent transactions
@@ -455,6 +480,12 @@ T setActive(@Nullable T ctx) {
return ctx;
}
+ void onError(SpannerException spannerException) {}
+
+ void onReadDone() {}
+
+ void onTransactionDone() {}
+
TraceWrapper getTracer() {
return tracer;
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
index 587e7763eee..f36da57a816 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
@@ -68,7 +68,6 @@
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
-import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ForwardingListenableFuture;
import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
@@ -88,6 +87,8 @@
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.metrics.Meter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -144,7 +145,7 @@ void maybeWaitOnMinSessions() {
"Timed out after waiting " + timeoutMillis + "ms for session pool creation");
}
- if (options.getUseMultiplexedSession()
+ if (useMultiplexedSessions()
&& !waitOnMultiplexedSessionsLatch.await(timeoutNanos, TimeUnit.NANOSECONDS)) {
final long timeoutMillis = options.getWaitForMinSessions().toMillis();
throw SpannerExceptionFactory.newSpannerException(
@@ -156,7 +157,8 @@ void maybeWaitOnMinSessions() {
}
}
- private abstract static class CachedResultSetSupplier implements Supplier {
+ private abstract static class CachedResultSetSupplier
+ implements com.google.common.base.Supplier {
private ResultSet cached;
@@ -577,6 +579,7 @@ public PooledSessionFuture replaceSession(
numSessionsInUse--;
numSessionsReleased++;
checkedOutSessions.remove(session);
+ markedCheckedOutSessions.remove(session);
}
session.leakedException = null;
invalidateSession(session.get());
@@ -1333,6 +1336,9 @@ void clearLeakedException() {
private void markCheckedOut() {
if (options.isTrackStackTraceOfSessionCheckout()) {
this.leakedException = new LeakedSessionException();
+ synchronized (SessionPool.this.lock) {
+ SessionPool.this.markedCheckedOutSessions.add(this);
+ }
}
}
@@ -1526,6 +1532,7 @@ public ApiFuture asyncClose() {
synchronized (lock) {
leakedException = null;
checkedOutSessions.remove(this);
+ markedCheckedOutSessions.remove(this);
}
}
return ApiFutures.immediateFuture(Empty.getDefaultInstance());
@@ -1835,7 +1842,7 @@ interface CachedSession extends Session {
class PooledSession implements CachedSession {
- @VisibleForTesting SessionImpl delegate;
+ @VisibleForTesting final SessionImpl delegate;
private volatile SpannerException lastException;
private volatile boolean allowReplacing = true;
@@ -1872,7 +1879,7 @@ class PooledSession implements CachedSession {
private SessionState state;
private PooledSession(SessionImpl delegate) {
- this.delegate = delegate;
+ this.delegate = Preconditions.checkNotNull(delegate);
this.state = SessionState.AVAILABLE;
// initialise the lastUseTime field for each session.
@@ -2265,7 +2272,6 @@ public String getName() {
@Override
public void close() {
synchronized (lock) {
- numMultiplexedSessionsReleased++;
if (lastException != null && isDatabaseOrInstanceNotFound(lastException)) {
SessionPool.this.resourceNotFoundException =
MoreObjects.firstNonNull(
@@ -2348,7 +2354,8 @@ private PooledSession pollUninterruptiblyWithTimeout(
"Timed out after waiting "
+ acquireSessionTimeout.toMillis()
+ "ms for acquiring session. To mitigate error SessionPoolOptions#setAcquireSessionTimeout(Duration) to set a higher timeout"
- + " or increase the number of sessions in the session pool.");
+ + " or increase the number of sessions in the session pool.\n"
+ + createCheckedOutSessionsStackTraces());
if (waiter.setException(exception)) {
// Only throw the exception if setting it on the waiter was successful. The
// waiter.setException(..) method returns false if some other thread in the meantime
@@ -2648,7 +2655,7 @@ private void removeLongRunningSessions(
void maintainMultiplexedSession(Instant currentTime) {
try {
- if (options.getUseMultiplexedSession()) {
+ if (useMultiplexedSessions()) {
if (currentMultiplexedSessionReference.get().isDone()) {
SessionReference sessionReference = getMultiplexedSessionInstance();
if (sessionReference != null
@@ -2771,15 +2778,9 @@ enum Position {
@GuardedBy("lock")
private long numSessionsAcquired = 0;
- @GuardedBy("lock")
- private long numMultiplexedSessionsAcquired = 0;
-
@GuardedBy("lock")
private long numSessionsReleased = 0;
- @GuardedBy("lock")
- private long numMultiplexedSessionsReleased = 0;
-
@GuardedBy("lock")
private long numIdleSessionsRemoved = 0;
@@ -2801,6 +2802,9 @@ enum Position {
@VisibleForTesting
final Set checkedOutSessions = new HashSet<>();
+ @GuardedBy("lock")
+ private final Set markedCheckedOutSessions = new HashSet<>();
+
private final SessionConsumer sessionConsumer = new SessionConsumerImpl();
private final MultiplexedSessionInitializationConsumer multiplexedSessionInitializationConsumer =
@@ -2830,7 +2834,9 @@ static SessionPool createPool(
SessionClient sessionClient,
TraceWrapper tracer,
List labelValues,
- Attributes attributes) {
+ Attributes attributes,
+ AtomicLong numMultiplexedSessionsAcquired,
+ AtomicLong numMultiplexedSessionsReleased) {
final SessionPoolOptions sessionPoolOptions = spannerOptions.getSessionPoolOptions();
// A clock instance is passed in {@code SessionPoolOptions} in order to allow mocking via tests.
@@ -2846,7 +2852,9 @@ static SessionPool createPool(
tracer,
labelValues,
spannerOptions.getOpenTelemetry(),
- attributes);
+ attributes,
+ numMultiplexedSessionsAcquired,
+ numMultiplexedSessionsReleased);
}
static SessionPool createPool(
@@ -2884,7 +2892,9 @@ static SessionPool createPool(
tracer,
SPANNER_DEFAULT_LABEL_VALUES,
openTelemetry,
- null);
+ null,
+ new AtomicLong(),
+ new AtomicLong());
}
static SessionPool createPool(
@@ -2898,7 +2908,9 @@ static SessionPool createPool(
TraceWrapper tracer,
List labelValues,
OpenTelemetry openTelemetry,
- Attributes attributes) {
+ Attributes attributes,
+ AtomicLong numMultiplexedSessionsAcquired,
+ AtomicLong numMultiplexedSessionsReleased) {
SessionPool pool =
new SessionPool(
poolOptions,
@@ -2912,7 +2924,9 @@ static SessionPool createPool(
tracer,
labelValues,
openTelemetry,
- attributes);
+ attributes,
+ numMultiplexedSessionsAcquired,
+ numMultiplexedSessionsReleased);
pool.initPool();
return pool;
}
@@ -2929,7 +2943,9 @@ private SessionPool(
TraceWrapper tracer,
List labelValues,
OpenTelemetry openTelemetry,
- Attributes attributes) {
+ Attributes attributes,
+ AtomicLong numMultiplexedSessionsAcquired,
+ AtomicLong numMultiplexedSessionsReleased) {
this.options = options;
this.databaseRole = databaseRole;
this.executorFactory = executorFactory;
@@ -2940,13 +2956,24 @@ private SessionPool(
this.initialReleasePosition = initialReleasePosition;
this.poolMaintainer = new PoolMaintainer();
this.tracer = tracer;
- this.initOpenCensusMetricsCollection(metricRegistry, labelValues);
- this.initOpenTelemetryMetricsCollection(openTelemetry, attributes);
+ this.initOpenCensusMetricsCollection(
+ metricRegistry,
+ labelValues,
+ numMultiplexedSessionsAcquired,
+ numMultiplexedSessionsReleased);
+ this.initOpenTelemetryMetricsCollection(
+ openTelemetry, attributes, numMultiplexedSessionsAcquired, numMultiplexedSessionsReleased);
this.waitOnMinSessionsLatch =
options.getMinSessions() > 0 ? new CountDownLatch(1) : new CountDownLatch(0);
this.waitOnMultiplexedSessionsLatch = new CountDownLatch(1);
}
+ // TODO: Remove once all code for multiplexed sessions has been removed from the pool.
+ private boolean useMultiplexedSessions() {
+ // Multiplexed sessions have moved to MultiplexedSessionDatabaseClient
+ return false;
+ }
+
/**
* @return the {@link Dialect} of the underlying database. This method will block until the
* dialect is available. It will potentially execute one or two RPCs to get the dialect if
@@ -2996,6 +3023,13 @@ int getNumberOfSessionsInUse() {
}
}
+ @VisibleForTesting
+ int getMaxSessionsInUse() {
+ synchronized (lock) {
+ return maxSessionsInUse;
+ }
+ }
+
@VisibleForTesting
double getRatioOfSessionsInUse() {
synchronized (lock) {
@@ -3071,7 +3105,7 @@ private void initPool() {
if (options.getMinSessions() > 0) {
createSessions(options.getMinSessions(), true);
}
- if (options.getUseMultiplexedSession()) {
+ if (useMultiplexedSessions()) {
maybeCreateMultiplexedSession(multiplexedSessionInitializationConsumer);
}
}
@@ -3137,10 +3171,10 @@ boolean isValid() {
/**
* Returns a multiplexed session. The method fallbacks to a regular session if {@link
- * SessionPoolOptions#useMultiplexedSession} is not set.
+ * SessionPoolOptions#getUseMultiplexedSession} is not set.
*/
SessionFutureWrapper getMultiplexedSessionWithFallback() throws SpannerException {
- if (options.getUseMultiplexedSession()) {
+ if (useMultiplexedSessions()) {
ISpan span = tracer.getCurrentSpan();
try {
return getWrappedMultiplexedSessionFuture(span);
@@ -3244,30 +3278,60 @@ private void incrementNumSessionsInUse(boolean isMultiplexed) {
maxSessionsInUse = numSessionsInUse;
}
numSessionsAcquired++;
- } else {
- numMultiplexedSessionsAcquired++;
}
}
}
private void maybeCreateSession() {
ISpan span = tracer.getCurrentSpan();
+ boolean throwResourceExhaustedException = false;
synchronized (lock) {
if (numWaiters() >= numSessionsBeingCreated) {
if (canCreateSession()) {
span.addAnnotation("Creating sessions");
createSessions(getAllowedCreateSessions(options.getIncStep()), false);
} else if (options.isFailIfPoolExhausted()) {
- span.addAnnotation("Pool exhausted. Failing");
- // throw specific exception
- throw newSpannerException(
- ErrorCode.RESOURCE_EXHAUSTED,
- "No session available in the pool. Maximum number of sessions in the pool can be"
- + " overridden by invoking SessionPoolOptions#Builder#setMaxSessions. Client can be made to block"
- + " rather than fail by setting SessionPoolOptions#Builder#setBlockIfPoolExhausted.");
+ throwResourceExhaustedException = true;
}
}
}
+ if (!throwResourceExhaustedException) {
+ return;
+ }
+ span.addAnnotation("Pool exhausted. Failing");
+
+ String message =
+ "No session available in the pool. Maximum number of sessions in the pool can be"
+ + " overridden by invoking SessionPoolOptions#Builder#setMaxSessions. Client can be made to block"
+ + " rather than fail by setting SessionPoolOptions#Builder#setBlockIfPoolExhausted.\n"
+ + createCheckedOutSessionsStackTraces();
+ throw newSpannerException(ErrorCode.RESOURCE_EXHAUSTED, message);
+ }
+
+ private StringBuilder createCheckedOutSessionsStackTraces() {
+ List currentlyCheckedOutSessions;
+ synchronized (lock) {
+ currentlyCheckedOutSessions = new ArrayList<>(this.markedCheckedOutSessions);
+ }
+
+ // Create the error message without holding the lock, as we are potentially looping through a
+ // large set, and analyzing a large number of stack traces.
+ StringBuilder stackTraces =
+ new StringBuilder(
+ "There are currently "
+ + currentlyCheckedOutSessions.size()
+ + " sessions checked out:\n\n");
+ if (options.isTrackStackTraceOfSessionCheckout()) {
+ for (PooledSessionFuture session : currentlyCheckedOutSessions) {
+ if (session.leakedException != null) {
+ StringWriter writer = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(writer);
+ session.leakedException.printStackTrace(printWriter);
+ stackTraces.append(writer).append("\n\n");
+ }
+ }
+ }
+ return stackTraces;
}
private void releaseSession(Tuple sessionWithPosition) {
@@ -3752,6 +3816,10 @@ public void onSessionReady(SessionImpl session) {
public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount) {
synchronized (lock) {
numSessionsBeingCreated -= createFailureForSessionCount;
+ if (numSessionsBeingCreated == 0) {
+ // Don't continue to block if no more sessions are being created.
+ waitOnMinSessionsLatch.countDown();
+ }
if (isClosed()) {
decrementPendingClosures(createFailureForSessionCount);
}
@@ -3765,7 +3833,10 @@ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount
* exporter, it allows users to monitor client behavior.
*/
private void initOpenCensusMetricsCollection(
- MetricRegistry metricRegistry, List labelValues) {
+ MetricRegistry metricRegistry,
+ List labelValues,
+ AtomicLong numMultiplexedSessionsAcquired,
+ AtomicLong numMultiplexedSessionsReleased) {
if (!SpannerOptions.isEnabledOpenCensusMetrics()) {
return;
}
@@ -3850,18 +3921,14 @@ private void initOpenCensusMetricsCollection(
labelValuesWithRegularSessions, this, sessionPool -> sessionPool.numSessionsAcquired);
numAcquiredSessionsMetric.removeTimeSeries(labelValuesWithMultiplexedSessions);
numAcquiredSessionsMetric.createTimeSeries(
- labelValuesWithMultiplexedSessions,
- this,
- sessionPool -> sessionPool.numMultiplexedSessionsAcquired);
+ labelValuesWithMultiplexedSessions, this, unused -> numMultiplexedSessionsAcquired.get());
numReleasedSessionsMetric.removeTimeSeries(labelValuesWithRegularSessions);
numReleasedSessionsMetric.createTimeSeries(
labelValuesWithRegularSessions, this, sessionPool -> sessionPool.numSessionsReleased);
numReleasedSessionsMetric.removeTimeSeries(labelValuesWithMultiplexedSessions);
numReleasedSessionsMetric.createTimeSeries(
- labelValuesWithMultiplexedSessions,
- this,
- sessionPool -> sessionPool.numMultiplexedSessionsReleased);
+ labelValuesWithMultiplexedSessions, this, unused -> numMultiplexedSessionsReleased.get());
List labelValuesWithBeingPreparedType = new ArrayList<>(labelValues);
labelValuesWithBeingPreparedType.add(NUM_SESSIONS_BEING_PREPARED);
@@ -3899,7 +3966,10 @@ private void initOpenCensusMetricsCollection(
* an exporter, it allows users to monitor client behavior.
*/
private void initOpenTelemetryMetricsCollection(
- OpenTelemetry openTelemetry, Attributes attributes) {
+ OpenTelemetry openTelemetry,
+ Attributes attributes,
+ AtomicLong numMultiplexedSessionsAcquired,
+ AtomicLong numMultiplexedSessionsReleased) {
if (openTelemetry == null || !SpannerOptions.isEnabledOpenTelemetryMetrics()) {
return;
}
@@ -3971,7 +4041,8 @@ private void initOpenTelemetryMetricsCollection(
.buildWithCallback(
measurement -> {
measurement.record(this.numSessionsAcquired, attributesRegularSession);
- measurement.record(this.numMultiplexedSessionsAcquired, attributesMultiplexedSession);
+ measurement.record(
+ numMultiplexedSessionsAcquired.get(), attributesMultiplexedSession);
});
meter
@@ -3981,7 +4052,8 @@ private void initOpenTelemetryMetricsCollection(
.buildWithCallback(
measurement -> {
measurement.record(this.numSessionsReleased, attributesRegularSession);
- measurement.record(this.numMultiplexedSessionsReleased, attributesMultiplexedSession);
+ measurement.record(
+ numMultiplexedSessionsReleased.get(), attributesMultiplexedSession);
});
}
}
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 4a048af52c0..382bef1b5a2 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
@@ -49,6 +49,7 @@ public class SessionPoolOptions {
private final ActionOnExhaustion actionOnExhaustion;
private final long loopFrequency;
+ private final java.time.Duration multiplexedSessionMaintenanceLoopFrequency;
private final int keepAliveIntervalMinutes;
private final Duration removeInactiveSessionAfter;
private final ActionOnSessionNotFound actionOnSessionNotFound;
@@ -72,6 +73,10 @@ public class SessionPoolOptions {
private final Clock poolMaintainerClock;
private final boolean useMultiplexedSession;
+
+ private final boolean useRandomChannelHint;
+
+ // TODO: Change to use java.time.Duration.
private final Duration multiplexedSessionMaintenanceDuration;
private SessionPoolOptions(Builder builder) {
@@ -89,6 +94,8 @@ private SessionPoolOptions(Builder builder) {
this.trackStackTraceOfSessionCheckout = builder.trackStackTraceOfSessionCheckout;
this.initialWaitForSessionTimeoutMillis = builder.initialWaitForSessionTimeoutMillis;
this.loopFrequency = builder.loopFrequency;
+ this.multiplexedSessionMaintenanceLoopFrequency =
+ builder.multiplexedSessionMaintenanceLoopFrequency;
this.keepAliveIntervalMinutes = builder.keepAliveIntervalMinutes;
this.removeInactiveSessionAfter = builder.removeInactiveSessionAfter;
this.autoDetectDialect = builder.autoDetectDialect;
@@ -98,7 +105,12 @@ private SessionPoolOptions(Builder builder) {
this.randomizePositionQPSThreshold = builder.randomizePositionQPSThreshold;
this.inactiveTransactionRemovalOptions = builder.inactiveTransactionRemovalOptions;
this.poolMaintainerClock = builder.poolMaintainerClock;
- this.useMultiplexedSession = builder.useMultiplexedSession;
+ // TODO: Remove when multiplexed sessions are guaranteed to be supported.
+ this.useMultiplexedSession =
+ builder.useMultiplexedSession
+ && !Boolean.parseBoolean(
+ System.getenv("GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS"));
+ this.useRandomChannelHint = builder.useRandomChannelHint;
this.multiplexedSessionMaintenanceDuration = builder.multiplexedSessionMaintenanceDuration;
}
@@ -121,6 +133,9 @@ public boolean equals(Object o) {
&& Objects.equals(
this.initialWaitForSessionTimeoutMillis, other.initialWaitForSessionTimeoutMillis)
&& Objects.equals(this.loopFrequency, other.loopFrequency)
+ && Objects.equals(
+ this.multiplexedSessionMaintenanceLoopFrequency,
+ other.multiplexedSessionMaintenanceLoopFrequency)
&& Objects.equals(this.keepAliveIntervalMinutes, other.keepAliveIntervalMinutes)
&& Objects.equals(this.removeInactiveSessionAfter, other.removeInactiveSessionAfter)
&& Objects.equals(this.autoDetectDialect, other.autoDetectDialect)
@@ -132,6 +147,7 @@ public boolean equals(Object o) {
this.inactiveTransactionRemovalOptions, other.inactiveTransactionRemovalOptions)
&& Objects.equals(this.poolMaintainerClock, other.poolMaintainerClock)
&& Objects.equals(this.useMultiplexedSession, other.useMultiplexedSession)
+ && Objects.equals(this.useRandomChannelHint, other.useRandomChannelHint)
&& Objects.equals(
this.multiplexedSessionMaintenanceDuration,
other.multiplexedSessionMaintenanceDuration);
@@ -151,6 +167,7 @@ public int hashCode() {
this.trackStackTraceOfSessionCheckout,
this.initialWaitForSessionTimeoutMillis,
this.loopFrequency,
+ this.multiplexedSessionMaintenanceLoopFrequency,
this.keepAliveIntervalMinutes,
this.removeInactiveSessionAfter,
this.autoDetectDialect,
@@ -161,6 +178,7 @@ public int hashCode() {
this.inactiveTransactionRemovalOptions,
this.poolMaintainerClock,
this.useMultiplexedSession,
+ this.useRandomChannelHint,
this.multiplexedSessionMaintenanceDuration);
}
@@ -204,6 +222,10 @@ long getLoopFrequency() {
return loopFrequency;
}
+ java.time.Duration getMultiplexedSessionMaintenanceLoopFrequency() {
+ return this.multiplexedSessionMaintenanceLoopFrequency;
+ }
+
public int getKeepAliveIntervalMinutes() {
return keepAliveIntervalMinutes;
}
@@ -290,6 +312,10 @@ public boolean getUseMultiplexedSession() {
return useMultiplexedSession;
}
+ boolean isUseRandomChannelHint() {
+ return useRandomChannelHint;
+ }
+
Duration getMultiplexedSessionMaintenanceDuration() {
return multiplexedSessionMaintenanceDuration;
}
@@ -476,6 +502,8 @@ public static class Builder {
private InactiveTransactionRemovalOptions inactiveTransactionRemovalOptions =
InactiveTransactionRemovalOptions.newBuilder().build();
private long loopFrequency = 10 * 1000L;
+ private java.time.Duration multiplexedSessionMaintenanceLoopFrequency =
+ java.time.Duration.ofMinutes(10);
private int keepAliveIntervalMinutes = 30;
private Duration removeInactiveSessionAfter = Duration.ofMinutes(55L);
private boolean autoDetectDialect = false;
@@ -492,6 +520,8 @@ public static class Builder {
private boolean useMultiplexedSession = getUseMultiplexedSessionFromEnvVariable();
+ private boolean useRandomChannelHint;
+
private Duration multiplexedSessionMaintenanceDuration = Duration.ofDays(7);
private Clock poolMaintainerClock = Clock.INSTANCE;
@@ -535,6 +565,8 @@ private Builder(SessionPoolOptions options) {
this.actionOnSessionLeak = options.actionOnSessionLeak;
this.trackStackTraceOfSessionCheckout = options.trackStackTraceOfSessionCheckout;
this.loopFrequency = options.loopFrequency;
+ this.multiplexedSessionMaintenanceLoopFrequency =
+ options.multiplexedSessionMaintenanceLoopFrequency;
this.keepAliveIntervalMinutes = options.keepAliveIntervalMinutes;
this.removeInactiveSessionAfter = options.removeInactiveSessionAfter;
this.autoDetectDialect = options.autoDetectDialect;
@@ -599,6 +631,11 @@ Builder setLoopFrequency(long loopFrequency) {
return this;
}
+ Builder setMultiplexedSessionMaintenanceLoopFrequency(java.time.Duration frequency) {
+ this.multiplexedSessionMaintenanceLoopFrequency = frequency;
+ return this;
+ }
+
Builder setInactiveTransactionRemovalOptions(
InactiveTransactionRemovalOptions inactiveTransactionRemovalOptions) {
this.inactiveTransactionRemovalOptions = inactiveTransactionRemovalOptions;
@@ -723,6 +760,11 @@ Builder setUseMultiplexedSession(boolean useMultiplexedSession) {
return this;
}
+ Builder setUseRandomChannelHint(boolean useRandomChannelHint) {
+ this.useRandomChannelHint = useRandomChannelHint;
+ return this;
+ }
+
@VisibleForTesting
Builder setMultiplexedSessionMaintenanceDuration(
Duration multiplexedSessionMaintenanceDuration) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
index 69c8be9a706..86b5de01c69 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
@@ -50,6 +50,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -247,7 +248,7 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
synchronized (this) {
checkClosed();
String clientId = null;
- if (dbClients.containsKey(db) && !dbClients.get(db).pool.isValid()) {
+ if (dbClients.containsKey(db) && !dbClients.get(db).isValid()) {
// Close the invalidated client and remove it.
dbClients.get(db).closeAsync(new ClosedException());
clientId = dbClients.get(db).clientId;
@@ -271,15 +272,32 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
attributesBuilder.put("database", db.getDatabase());
attributesBuilder.put("instance_id", db.getInstanceId().getName());
+ boolean useMultiplexedSession =
+ getOptions().getSessionPoolOptions().getUseMultiplexedSession();
+ MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient =
+ useMultiplexedSession
+ ? new MultiplexedSessionDatabaseClient(SpannerImpl.this.getSessionClient(db))
+ : null;
+ AtomicLong numMultiplexedSessionsAcquired =
+ useMultiplexedSession
+ ? multiplexedSessionDatabaseClient.getNumSessionsAcquired()
+ : new AtomicLong();
+ AtomicLong numMultiplexedSessionsReleased =
+ useMultiplexedSession
+ ? multiplexedSessionDatabaseClient.getNumSessionsReleased()
+ : new AtomicLong();
SessionPool pool =
SessionPool.createPool(
getOptions(),
SpannerImpl.this.getSessionClient(db),
this.tracer,
labelValues,
- attributesBuilder.build());
+ attributesBuilder.build(),
+ numMultiplexedSessionsAcquired,
+ numMultiplexedSessionsReleased);
pool.maybeWaitOnMinSessions();
- DatabaseClientImpl dbClient = createDatabaseClient(clientId, pool);
+ DatabaseClientImpl dbClient =
+ createDatabaseClient(clientId, pool, multiplexedSessionDatabaseClient);
dbClients.put(db, dbClient);
return dbClient;
}
@@ -287,8 +305,11 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
}
@VisibleForTesting
- DatabaseClientImpl createDatabaseClient(String clientId, SessionPool pool) {
- return new DatabaseClientImpl(clientId, pool, tracer);
+ DatabaseClientImpl createDatabaseClient(
+ String clientId,
+ SessionPool pool,
+ @Nullable MultiplexedSessionDatabaseClient multiplexedSessionClient) {
+ return new DatabaseClientImpl(clientId, pool, multiplexedSessionClient, tracer);
}
@Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
index 2d7cf812121..4deeeb92af8 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
@@ -155,12 +155,12 @@ public ApiFuture setCallback(Executor exec, ReadyCallback cb) {
@Override
public void addListener(Runnable listener) {
- ((ListenableAsyncResultSet) this.delegate).addListener(listener);
+ ((ListenableAsyncResultSet) getDelegate()).addListener(listener);
}
@Override
public void removeListener(Runnable listener) {
- ((ListenableAsyncResultSet) this.delegate).removeListener(listener);
+ ((ListenableAsyncResultSet) getDelegate()).removeListener(listener);
}
}
@@ -601,6 +601,8 @@ String getTransactionTag() {
@Override
public SpannerException onError(SpannerException e, boolean withBeginTransaction) {
+ e = super.onError(e, withBeginTransaction);
+
// If the statement that caused an error was the statement that included a BeginTransaction
// option, we simulate an aborted transaction to force a retry of the entire transaction. This
// will cause the retry to execute an explicit BeginTransaction RPC and then the actual
@@ -1118,4 +1120,7 @@ public CommitResponse getCommitResponse() {
public void invalidate() {
isValid = false;
}
+
+ @Override
+ public void close() {}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
index c0859b57903..e1a4415ea49 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
@@ -773,6 +773,12 @@ default boolean isDelayTransactionStartUntilFirstWrite() {
/** Sets how savepoints should be supported on this connection. */
void setSavepointSupport(SavepointSupport savepointSupport);
+ /** Returns the current {@link DdlInTransactionMode} for this connection. */
+ DdlInTransactionMode getDdlInTransactionMode();
+
+ /** Sets how the connection should behave if a DDL statement is executed during a transaction. */
+ void setDdlInTransactionMode(DdlInTransactionMode ddlInTransactionMode);
+
/**
* Creates a savepoint with the given name.
*
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
index d0cb7169793..70e789eb580 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
@@ -243,6 +243,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
private QueryOptions queryOptions = QueryOptions.getDefaultInstance();
private RpcPriority rpcPriority = null;
private SavepointSupport savepointSupport = SavepointSupport.FAIL_AFTER_ROLLBACK;
+ private DdlInTransactionMode ddlInTransactionMode;
private String transactionTag;
private String statementTag;
@@ -271,6 +272,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
this.autocommit = options.isAutocommit();
this.queryOptions = this.queryOptions.toBuilder().mergeFrom(options.getQueryOptions()).build();
this.rpcPriority = options.getRPCPriority();
+ this.ddlInTransactionMode = options.getDdlInTransactionMode();
this.returnCommitStats = options.isReturnCommitStats();
this.delayTransactionStartUntilFirstWrite = options.isDelayTransactionStartUntilFirstWrite();
this.dataBoostEnabled = options.isDataBoostEnabled();
@@ -296,6 +298,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
new StatementExecutor(options.isUseVirtualThreads(), Collections.emptyList());
this.spannerPool = Preconditions.checkNotNull(spannerPool);
this.options = Preconditions.checkNotNull(options);
+ this.ddlInTransactionMode = options.getDdlInTransactionMode();
this.spanner = spannerPool.getSpanner(options, this);
this.ddlClient = Preconditions.checkNotNull(ddlClient);
this.dbClient = Preconditions.checkNotNull(dbClient);
@@ -571,6 +574,21 @@ public RpcPriority getRPCPriority() {
return this.rpcPriority;
}
+ @Override
+ public DdlInTransactionMode getDdlInTransactionMode() {
+ return this.ddlInTransactionMode;
+ }
+
+ @Override
+ public void setDdlInTransactionMode(DdlInTransactionMode ddlInTransactionMode) {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ ConnectionPreconditions.checkState(
+ !isBatchActive(), "Cannot set DdlInTransactionMode while in a batch");
+ ConnectionPreconditions.checkState(
+ !isTransactionStarted(), "Cannot set DdlInTransactionMode while a transaction is active");
+ this.ddlInTransactionMode = Preconditions.checkNotNull(ddlInTransactionMode);
+ }
+
@Override
public void setStatementTimeout(long timeout, TimeUnit unit) {
Preconditions.checkArgument(timeout > 0L, "Zero or negative timeout values are not allowed");
@@ -1639,7 +1657,16 @@ private ApiFuture internalExecuteBatchUpdateAsync(
}
private UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() {
- return getCurrentUnitOfWorkOrStartNewUnitOfWork(false);
+ return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.UNKNOWN, false);
+ }
+
+ @VisibleForTesting
+ UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) {
+ return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.UNKNOWN, isInternalMetadataQuery);
+ }
+
+ private UnitOfWork getOrStartDdlUnitOfWork() {
+ return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.DDL, false);
}
/**
@@ -1647,20 +1674,38 @@ private UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() {
* current transaction settings of the connection and returns that.
*/
@VisibleForTesting
- UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) {
+ UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(
+ StatementType statementType, boolean isInternalMetadataQuery) {
if (isInternalMetadataQuery) {
// Just return a temporary single-use transaction.
- return createNewUnitOfWork(true);
+ return createNewUnitOfWork(/* isInternalMetadataQuery = */ true, /* forceSingleUse = */ true);
}
+ maybeAutoCommitCurrentTransaction(statementType);
if (this.currentUnitOfWork == null || !this.currentUnitOfWork.isActive()) {
- this.currentUnitOfWork = createNewUnitOfWork(false);
+ this.currentUnitOfWork =
+ createNewUnitOfWork(
+ /* isInternalMetadataQuery = */ false,
+ /* forceSingleUse = */ statementType == StatementType.DDL
+ && this.ddlInTransactionMode != DdlInTransactionMode.FAIL
+ && !this.transactionBeginMarked);
}
return this.currentUnitOfWork;
}
+ void maybeAutoCommitCurrentTransaction(StatementType statementType) {
+ if (this.currentUnitOfWork instanceof ReadWriteTransaction
+ && this.currentUnitOfWork.isActive()
+ && statementType == StatementType.DDL
+ && this.ddlInTransactionMode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
+ commit();
+ }
+ }
+
@VisibleForTesting
- UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
- if (isInternalMetadataQuery || (isAutocommit() && !isInTransaction() && !isInBatch())) {
+ UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery, boolean forceSingleUse) {
+ if (isInternalMetadataQuery
+ || (isAutocommit() && !isInTransaction() && !isInBatch())
+ || forceSingleUse) {
return SingleUseTransaction.newBuilder()
.setInternalMetadataQuery(isInternalMetadataQuery)
.setDdlClient(ddlClient)
@@ -1741,7 +1786,7 @@ private void popUnitOfWorkFromTransactionStack() {
}
private ApiFuture executeDdlAsync(CallType callType, ParsedStatement ddl) {
- return getCurrentUnitOfWorkOrStartNewUnitOfWork().executeDdlAsync(callType, ddl);
+ return getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
}
@Override
@@ -1788,15 +1833,23 @@ public void startBatchDdl() {
ConnectionPreconditions.checkState(
!isReadOnly(), "Cannot start a DDL batch when the connection is in read-only mode");
ConnectionPreconditions.checkState(
- !isTransactionStarted(), "Cannot start a DDL batch while a transaction is active");
+ !isTransactionStarted()
+ || getDdlInTransactionMode() == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION,
+ "Cannot start a DDL batch while a transaction is active");
ConnectionPreconditions.checkState(
!(isAutocommit() && isInTransaction()),
"Cannot start a DDL batch while in a temporary transaction");
ConnectionPreconditions.checkState(
!transactionBeginMarked, "Cannot start a DDL batch when a transaction has begun");
+ ConnectionPreconditions.checkState(
+ isAutocommit() || getDdlInTransactionMode() != DdlInTransactionMode.FAIL,
+ "Cannot start a DDL batch when autocommit=false and ddlInTransactionMode=FAIL");
+
+ maybeAutoCommitCurrentTransaction(StatementType.DDL);
this.batchMode = BatchMode.DDL;
this.unitOfWorkType = UnitOfWorkType.DDL_BATCH;
- this.currentUnitOfWork = createNewUnitOfWork(false);
+ this.currentUnitOfWork =
+ createNewUnitOfWork(/* isInternalMetadataQuery = */ false, /* forceSingleUse = */ false);
}
@Override
@@ -1814,7 +1867,8 @@ public void startBatchDml() {
// Then create the DML batch.
this.batchMode = BatchMode.DML;
this.unitOfWorkType = UnitOfWorkType.DML_BATCH;
- this.currentUnitOfWork = createNewUnitOfWork(false);
+ this.currentUnitOfWork =
+ createNewUnitOfWork(/* isInternalMetadataQuery = */ false, /* forceSingleUse = */ false);
}
@Override
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 f79a764a94c..59c30789afb 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
@@ -183,6 +183,8 @@ public String[] getValidValues() {
private static final String DEFAULT_OPTIMIZER_VERSION = "";
private static final String DEFAULT_OPTIMIZER_STATISTICS_PACKAGE = "";
private static final RpcPriority DEFAULT_RPC_PRIORITY = null;
+ private static final DdlInTransactionMode DEFAULT_DDL_IN_TRANSACTION_MODE =
+ DdlInTransactionMode.ALLOW_IN_EMPTY_TRANSACTION;
private static final boolean DEFAULT_RETURN_COMMIT_STATS = false;
private static final boolean DEFAULT_LENIENT = false;
private static final boolean DEFAULT_ROUTE_TO_LEADER = true;
@@ -253,6 +255,8 @@ public String[] getValidValues() {
public static final String LENIENT_PROPERTY_NAME = "lenient";
/** Name of the 'rpcPriority' connection property. */
public static final String RPC_PRIORITY_NAME = "rpcPriority";
+
+ public static final String DDL_IN_TRANSACTION_MODE_PROPERTY_NAME = "ddlInTransactionMode";
/** Dialect to use for a connection. */
private static final String DIALECT_PROPERTY_NAME = "dialect";
/** Name of the 'databaseRole' connection property. */
@@ -374,6 +378,11 @@ private static String generateGuardedConnectionPropertyError(
ConnectionProperty.createStringProperty(
RPC_PRIORITY_NAME,
"Sets the priority for all RPC invocations from this connection (HIGH/MEDIUM/LOW). The default is HIGH."),
+ ConnectionProperty.createStringProperty(
+ DDL_IN_TRANSACTION_MODE_PROPERTY_NAME,
+ "Sets the behavior of a connection when a DDL statement is executed in a read/write transaction. The default is "
+ + DEFAULT_DDL_IN_TRANSACTION_MODE
+ + "."),
ConnectionProperty.createStringProperty(
DIALECT_PROPERTY_NAME,
"Sets the dialect to use for new databases that are created by this connection."),
@@ -697,6 +706,7 @@ public static Builder newBuilder() {
private final boolean autoConfigEmulator;
private final Dialect dialect;
private final RpcPriority rpcPriority;
+ private final DdlInTransactionMode ddlInTransactionMode;
private final boolean delayTransactionStartUntilFirstWrite;
private final boolean trackSessionLeaks;
private final boolean trackConnectionLeaks;
@@ -757,6 +767,7 @@ private ConnectionOptions(Builder builder) {
determineHost(
matcher, parseEndpoint(this.uri), autoConfigEmulator, usePlainText, System.getenv());
this.rpcPriority = parseRPCPriority(this.uri);
+ this.ddlInTransactionMode = parseDdlInTransactionMode(this.uri);
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
this.trackConnectionLeaks = parseTrackConnectionLeaks(this.uri);
@@ -1195,6 +1206,14 @@ static RpcPriority parseRPCPriority(String uri) {
return value != null ? RpcPriority.valueOf(value) : DEFAULT_RPC_PRIORITY;
}
+ @VisibleForTesting
+ static DdlInTransactionMode parseDdlInTransactionMode(String uri) {
+ String value = parseUriProperty(uri, DDL_IN_TRANSACTION_MODE_PROPERTY_NAME);
+ return value != null
+ ? DdlInTransactionMode.valueOf(value.toUpperCase())
+ : DEFAULT_DDL_IN_TRANSACTION_MODE;
+ }
+
@VisibleForTesting
static String parseUriProperty(String uri, String property) {
Pattern pattern = Pattern.compile(String.format("(?is)(?:;|\\?)%s=(.*?)(?:;|$)", property));
@@ -1466,6 +1485,10 @@ RpcPriority getRPCPriority() {
return rpcPriority;
}
+ DdlInTransactionMode getDdlInTransactionMode() {
+ return this.ddlInTransactionMode;
+ }
+
/**
* Whether connections created by this {@link ConnectionOptions} should delay the actual start of
* a read/write transaction until the first write operation.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlInTransactionMode.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlInTransactionMode.java
new file mode 100644
index 00000000000..16645e929cb
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlInTransactionMode.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.connection;
+
+/** Enum used for setting the behavior of DDL in read/write transactions. */
+public enum DdlInTransactionMode {
+ /** All DDL statements in a read/write transaction fail. */
+ FAIL,
+ /**
+ * DDL statements in an empty transaction are allowed. That is; if the connection is in
+ * AutoCommit=false mode and no other statement has been executed, then executing a DDL statement
+ * or a DDL batch is allowed.
+ */
+ ALLOW_IN_EMPTY_TRANSACTION,
+ /**
+ * DDL statements automatically cause the current transaction to be committed and the DDL
+ * statement is subsequently executed without a transaction. This is equal to how MySQL and Oracle
+ * behave.
+ */
+ AUTO_COMMIT_TRANSACTION;
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java
index f3c93c6dd1c..a126bd8ff37 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java
@@ -28,6 +28,7 @@
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.Duration;
+import io.opentelemetry.context.Context;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
@@ -147,13 +148,16 @@ org.threeten.bp.Duration asDuration() {
/** Creates an {@link ExecutorService} for a {@link StatementExecutor}. */
private static ListeningExecutorService createExecutorService(boolean useVirtualThreads) {
return MoreExecutors.listeningDecorator(
- new ThreadPoolExecutor(
- 1,
- 1,
- 0L,
- TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<>(),
- useVirtualThreads ? DEFAULT_VIRTUAL_THREAD_FACTORY : DEFAULT_DAEMON_THREAD_FACTORY));
+ Context.taskWrapping(
+ new ThreadPoolExecutor(
+ 1,
+ 1,
+ 0L,
+ TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(),
+ useVirtualThreads
+ ? DEFAULT_VIRTUAL_THREAD_FACTORY
+ : DEFAULT_DAEMON_THREAD_FACTORY)));
}
private final ListeningExecutorService executor;
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java
index 07f7967c2aa..a209ff07112 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java
@@ -261,12 +261,11 @@ public void asyncRunnerWaitsUntilAsyncUpdateHasFinished() throws Exception {
executor);
res.get();
if (isMultiplexedSessionsEnabled()) {
+ // The mock server could have received a CreateSession request for a multiplexed session, but
+ // it could also be that that request has not yet reached the server.
assertThat(mockSpanner.getRequestTypes())
- .containsExactly(
- CreateSessionRequest.class,
- BatchCreateSessionsRequest.class,
- ExecuteSqlRequest.class,
- CommitRequest.class);
+ .containsAtLeast(
+ BatchCreateSessionsRequest.class, ExecuteSqlRequest.class, CommitRequest.class);
} else {
assertThat(mockSpanner.getRequestTypes())
.containsExactly(
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java
index 09d14cee3bf..e96ea3a6a4b 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java
@@ -898,49 +898,29 @@ public void asyncTransactionManagerBatchUpdateAbortedWithoutGettingResult() thro
}
}
assertThat(attempt.get()).isEqualTo(2);
- Iterable> requests = mockSpanner.getRequestTypes();
+ List> requests = mockSpanner.getRequestTypes();
+ // Remove the CreateSession requests for multiplexed sessions, as those are not relevant for
+ // this test.
+ requests.removeIf(request -> request == CreateSessionRequest.class);
int size = Iterables.size(requests);
assertThat(size).isIn(Range.closed(5, 6));
- if (isMultiplexedSessionsEnabled()) {
- if (size == 6) {
- assertThat(requests)
- .containsExactly(
- CreateSessionRequest.class,
- BatchCreateSessionsRequest.class,
- ExecuteBatchDmlRequest.class,
- BeginTransactionRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class);
- } else {
- assertThat(requests)
- .containsExactly(
- CreateSessionRequest.class,
- BatchCreateSessionsRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class,
- BeginTransactionRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class);
- }
+ if (size == 5) {
+ assertThat(requests)
+ .containsExactly(
+ BatchCreateSessionsRequest.class,
+ ExecuteBatchDmlRequest.class,
+ BeginTransactionRequest.class,
+ ExecuteBatchDmlRequest.class,
+ CommitRequest.class);
} else {
- if (size == 5) {
- assertThat(requests)
- .containsExactly(
- BatchCreateSessionsRequest.class,
- ExecuteBatchDmlRequest.class,
- BeginTransactionRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class);
- } else {
- assertThat(requests)
- .containsExactly(
- BatchCreateSessionsRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class,
- BeginTransactionRequest.class,
- ExecuteBatchDmlRequest.class,
- CommitRequest.class);
- }
+ assertThat(requests)
+ .containsExactly(
+ BatchCreateSessionsRequest.class,
+ ExecuteBatchDmlRequest.class,
+ CommitRequest.class,
+ BeginTransactionRequest.class,
+ ExecuteBatchDmlRequest.class,
+ CommitRequest.class);
}
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java
index 4b1d3361cc6..30e06719181 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java
@@ -18,12 +18,11 @@
import static io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import com.google.cloud.NoCredentials;
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ListValue;
@@ -43,16 +42,13 @@
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
import java.io.IOException;
import java.net.InetSocketAddress;
-import java.util.ArrayList;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
-import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.After;
@@ -223,7 +219,7 @@ public void testCreatesNumChannels() {
try (Spanner spanner = createSpannerOptions().getService()) {
assumeFalse(
"GRPC-GCP is currently not supported with multiplexed sessions",
- isMultiplexedSessionsEnabled(spanner));
+ isMultiplexedSessionsEnabled(spanner) && enableGcpPool);
DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
try (ResultSet resultSet = client.singleUse().executeQuery(SELECT1)) {
while (resultSet.next()) {}
@@ -233,34 +229,38 @@ public void testCreatesNumChannels() {
}
@Test
- public void testUsesAllChannels() throws InterruptedException, ExecutionException {
+ public void testUsesAllChannels() throws InterruptedException {
+ final int multiplier = 2;
try (Spanner spanner = createSpannerOptions().getService()) {
assumeFalse(
"GRPC-GCP is currently not supported with multiplexed sessions",
isMultiplexedSessionsEnabled(spanner));
DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
ListeningExecutorService executor =
- MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numChannels * 2));
- CountDownLatch latch = new CountDownLatch(numChannels * 2);
- List> futures = new ArrayList<>(numChannels * 2);
- for (int run = 0; run < numChannels * 2; run++) {
- futures.add(
- executor.submit(
- () -> {
- try (ReadOnlyTransaction transaction = client.readOnlyTransaction()) {
- try (ResultSet resultSet = transaction.executeQuery(SELECT1)) {
- while (resultSet.next()) {}
- latch.countDown();
- try {
- return latch.await(10L, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw SpannerExceptionFactory.asSpannerException(e);
- }
- }
- }
- }));
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numChannels * multiplier));
+ CountDownLatch latch = new CountDownLatch(numChannels * multiplier);
+ for (int run = 0; run < numChannels * multiplier; run++) {
+ executor.submit(
+ () -> {
+ // Use a multi-use read-only transaction to make sure we keep a session in use for
+ // a longer period of time.
+ try (ReadOnlyTransaction transaction = client.readOnlyTransaction()) {
+ try (ResultSet resultSet = transaction.executeQuery(SELECT1)) {
+ while (resultSet.next()) {}
+ }
+ latch.countDown();
+ // Wait here until we now that all threads have reached this point and have a
+ // session in use.
+ latch.await();
+ try (ResultSet resultSet = transaction.executeQuery(SELECT1)) {
+ while (resultSet.next()) {}
+ }
+ }
+ return true;
+ });
}
- assertEquals(numChannels * 2, Futures.allAsList(futures).get().size());
+ executor.shutdown();
+ assertTrue(executor.awaitTermination(Duration.ofSeconds(10L)));
}
assertEquals(numChannels, executeSqlLocalIps.size());
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
index 2a0c9c77c36..41da46a56a8 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
@@ -79,6 +79,7 @@
import com.google.spanner.v1.BatchWriteResponse;
import com.google.spanner.v1.BeginTransactionRequest;
import com.google.spanner.v1.CommitRequest;
+import com.google.spanner.v1.CreateSessionRequest;
import com.google.spanner.v1.DeleteSessionRequest;
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.DirectedReadOptions.IncludeReplicas;
@@ -3005,43 +3006,72 @@ public void testDatabaseOrInstanceDoesNotExistOnInitialization() throws Exceptio
@Test
public void testDatabaseOrInstanceDoesNotExistOnCreate() {
- StatusRuntimeException[] exceptions =
- new StatusRuntimeException[] {
- SpannerExceptionFactoryTest.newStatusResourceNotFoundException(
- "Database", SpannerExceptionFactory.DATABASE_RESOURCE_TYPE, DATABASE_NAME),
- SpannerExceptionFactoryTest.newStatusResourceNotFoundException(
- "Instance", SpannerExceptionFactory.INSTANCE_RESOURCE_TYPE, INSTANCE_NAME)
- };
- for (StatusRuntimeException exception : exceptions) {
- mockSpanner.setCreateSessionExecutionTime(
- SimulatedExecutionTime.ofStickyException(exception));
- mockSpanner.setBatchCreateSessionsExecutionTime(
- SimulatedExecutionTime.ofStickyException(exception));
- // Ensure there are no sessions in the pool by default.
- try (Spanner spanner =
- SpannerOptions.newBuilder()
- .setProjectId(TEST_PROJECT)
- .setChannelProvider(channelProvider)
- .setCredentials(NoCredentials.getInstance())
- .setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build())
- .build()
- .getService()) {
- DatabaseClientImpl dbClient =
- (DatabaseClientImpl)
- spanner.getDatabaseClient(
- DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
- // The create session failure should propagate to the client and not retry.
- try (ResultSet rs = dbClient.singleUse().executeQuery(SELECT1)) {
- assertThrows(ResourceNotFoundException.class, rs::next);
- // The server should only receive one BatchCreateSessions request.
- assertThat(mockSpanner.getRequests()).hasSize(1);
+ for (Duration waitForMinSessions : ImmutableList.of(Duration.ZERO, Duration.ofSeconds(5L))) {
+ StatusRuntimeException[] exceptions =
+ new StatusRuntimeException[] {
+ SpannerExceptionFactoryTest.newStatusResourceNotFoundException(
+ "Database", SpannerExceptionFactory.DATABASE_RESOURCE_TYPE, DATABASE_NAME),
+ SpannerExceptionFactoryTest.newStatusResourceNotFoundException(
+ "Instance", SpannerExceptionFactory.INSTANCE_RESOURCE_TYPE, INSTANCE_NAME)
+ };
+ for (StatusRuntimeException exception : exceptions) {
+ mockSpanner.setCreateSessionExecutionTime(
+ SimulatedExecutionTime.ofStickyException(exception));
+ mockSpanner.setBatchCreateSessionsExecutionTime(
+ SimulatedExecutionTime.ofStickyException(exception));
+ // Ensure there are no sessions in the pool by default.
+ try (Spanner spanner =
+ SpannerOptions.newBuilder()
+ .setProjectId(TEST_PROJECT)
+ .setChannelProvider(channelProvider)
+ .setCredentials(NoCredentials.getInstance())
+ .setSessionPoolOption(
+ SessionPoolOptions.newBuilder()
+ .setMinSessions(0)
+ .setWaitForMinSessions(waitForMinSessions)
+ .build())
+ .build()
+ .getService()) {
+ boolean useMultiplexedSession =
+ spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession();
+ DatabaseId databaseId = DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE);
+ if (useMultiplexedSession && !waitForMinSessions.isZero()) {
+ assertThrows(
+ ResourceNotFoundException.class, () -> spanner.getDatabaseClient(databaseId));
+ } else {
+ // Freeze the server until we try to execute ResultSet#next() to prevent the creation of
+ // a multiplexed session to finish before we try to use it.
+ mockSpanner.freeze();
+ DatabaseClientImpl dbClient =
+ (DatabaseClientImpl) spanner.getDatabaseClient(databaseId);
+ // The CreateSession / BatchCreateSessions failure should propagate to the client and
+ // not retry.
+ try (ResultSet rs = dbClient.singleUse().executeQuery(SELECT1)) {
+ mockSpanner.unfreeze();
+ assertThrows(ResourceNotFoundException.class, rs::next);
+ // The server should only receive one BatchCreateSessions request.
+ assertThat(mockSpanner.getRequests()).hasSize(1);
+ }
+ assertThrows(
+ ResourceNotFoundException.class,
+ () ->
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT)));
+ // No additional requests should have been sent by the client.
+ // Note that in case of the use of multiplexed sessions, then we have 2 requests:
+ // 1. BatchCreateSessions for the session pool.
+ // 2. CreateSession for the multiplexed session.
+ assertThat(mockSpanner.getRequests())
+ .hasSize(
+ spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()
+ ? 2
+ : 1);
+ }
}
- assertThrows(ResourceNotFoundException.class, dbClient::readWriteTransaction);
- // No additional requests should have been sent by the client.
- assertThat(mockSpanner.getRequests()).hasSize(1);
+ mockSpanner.reset();
+ mockSpanner.removeAllExecutionTimes();
}
- mockSpanner.reset();
- mockSpanner.removeAllExecutionTimes();
}
}
@@ -3146,8 +3176,16 @@ public void testDatabaseOrInstanceIsDeletedAndThenRecreated() throws Exception {
// receive any new requests.
mockSpanner.reset();
// All subsequent calls should fail with a DatabaseNotFoundException.
- assertThrows(
- ResourceNotFoundException.class, () -> dbClient.singleUse().executeQuery(SELECT1));
+
+ if (!spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()) {
+ // We only verify this for read-only transactions if we are not using multiplexed
+ // sessions. For multiplexed sessions, we don't need any special handling, as deleting the
+ // database will also invalidate the multiplexed session, and trying to continue to use it
+ // will continue to return an error.
+ assertThrows(
+ ResourceNotFoundException.class, () -> dbClient.singleUse().executeQuery(SELECT1));
+ }
+
assertThrows(
ResourceNotFoundException.class,
() -> dbClient.readWriteTransaction().run(transaction -> null));
@@ -3204,10 +3242,17 @@ public void testGetInvalidatedClientMultipleTimes() {
assertThrows(
ResourceNotFoundException.class,
() -> dbClient.singleUse().executeQuery(SELECT1).next());
- // The server should only receive one BatchCreateSessions request for each run as we
- // have set MinSessions=0.
- assertThat(mockSpanner.getRequests()).hasSize(run + 1);
- assertThat(dbClient.pool.isValid()).isFalse();
+ if (spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()) {
+ // We should only receive 1 CreateSession request. The query should never be executed,
+ // as the session creation fails before it gets to executing a query.
+ assertEquals(1, mockSpanner.countRequestsOfType(CreateSessionRequest.class));
+ assertEquals(0, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ } else {
+ // The server should only receive one BatchCreateSessions request for each run as we
+ // have set MinSessions=0.
+ assertThat(mockSpanner.getRequests()).hasSize(run + 1);
+ assertThat(dbClient.pool.isValid()).isFalse();
+ }
}
}
}
@@ -3653,29 +3698,53 @@ public void testClientIdReusedOnDatabaseNotFound() {
@Test
public void testBatchCreateSessionsPermissionDenied() {
- mockSpanner.setBatchCreateSessionsExecutionTime(
- SimulatedExecutionTime.ofStickyException(
- Status.PERMISSION_DENIED.withDescription("Not permitted").asRuntimeException()));
- mockSpanner.setCreateSessionExecutionTime(
- SimulatedExecutionTime.ofStickyException(
- Status.PERMISSION_DENIED.withDescription("Not permitted").asRuntimeException()));
- try (Spanner spanner =
- SpannerOptions.newBuilder()
- .setProjectId("my-project")
- .setChannelProvider(channelProvider)
- .setCredentials(NoCredentials.getInstance())
- .build()
- .getService()) {
- DatabaseId databaseId = DatabaseId.of("my-project", "my-instance", "my-database");
- DatabaseClient client = spanner.getDatabaseClient(databaseId);
- // The following call is non-blocking and will not generate an exception.
- ResultSet rs = client.singleUse().executeQuery(SELECT1);
- // Actually trying to get any results will cause an exception.
- SpannerException e = assertThrows(SpannerException.class, rs::next);
- assertEquals(ErrorCode.PERMISSION_DENIED, e.getErrorCode());
- } finally {
- mockSpanner.setBatchCreateSessionsExecutionTime(SimulatedExecutionTime.none());
- mockSpanner.setCreateSessionExecutionTime(SimulatedExecutionTime.none());
+ for (Duration waitForMinSessions : ImmutableList.of(Duration.ZERO, Duration.ofSeconds(5L))) {
+ mockSpanner.setBatchCreateSessionsExecutionTime(
+ SimulatedExecutionTime.ofStickyException(
+ Status.PERMISSION_DENIED.withDescription("Not permitted").asRuntimeException()));
+ mockSpanner.setCreateSessionExecutionTime(
+ SimulatedExecutionTime.ofStickyException(
+ Status.PERMISSION_DENIED.withDescription("Not permitted").asRuntimeException()));
+ if (waitForMinSessions.isZero()) {
+ mockSpanner.freeze();
+ }
+ try (Spanner spanner =
+ SpannerOptions.newBuilder()
+ .setProjectId("my-project")
+ .setChannelProvider(channelProvider)
+ .setCredentials(NoCredentials.getInstance())
+ .setSessionPoolOption(
+ SessionPoolOptions.newBuilder().setWaitForMinSessions(waitForMinSessions).build())
+ .build()
+ .getService()) {
+ DatabaseId databaseId = DatabaseId.of("my-project", "my-instance", "my-database");
+ SpannerException spannerException;
+ if (waitForMinSessions.isZero()) {
+ // The following call is non-blocking and will not generate an exception.
+ DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ ResultSet resultSet = client.singleUse().executeQuery(SELECT1);
+ mockSpanner.unfreeze();
+ // Actually trying to get any results will cause an exception.
+ spannerException = assertThrows(SpannerException.class, resultSet::next);
+ } else {
+ // This is blocking when we should wait for min sessions, and will therefore fail.
+ if (spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()) {
+ spannerException =
+ assertThrows(SpannerException.class, () -> spanner.getDatabaseClient(databaseId));
+ } else {
+ // TODO: Fix the session pool implementation for waiting for min sessions, so this also
+ // propagates the error directly when session creation fails.
+ DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ spannerException =
+ assertThrows(
+ SpannerException.class, () -> client.singleUse().executeQuery(SELECT1).next());
+ }
+ }
+ assertEquals(ErrorCode.PERMISSION_DENIED, spannerException.getErrorCode());
+ } finally {
+ mockSpanner.setBatchCreateSessionsExecutionTime(SimulatedExecutionTime.none());
+ mockSpanner.setCreateSessionExecutionTime(SimulatedExecutionTime.none());
+ }
}
}
@@ -3790,12 +3859,14 @@ public void testCreateSessionsFailure_shouldNotPropagateToCloseMethod() {
// Simulate session creation failures on the backend.
mockSpanner.setCreateSessionExecutionTime(
SimulatedExecutionTime.ofStickyException(Status.RESOURCE_EXHAUSTED.asRuntimeException()));
+ // This will not cause any failure as getting a session from the pool is guaranteed to be
+ // non-blocking, and any exceptions will be delayed until actual query execution.
+ mockSpanner.freeze();
DatabaseClient client =
spannerWithEmptySessionPool.getDatabaseClient(
DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
- // This will not cause any failure as getting a session from the pool is guaranteed to be
- // non-blocking, and any exceptions will be delayed until actual query execution.
try (ResultSet rs = client.singleUse().executeQuery(SELECT1)) {
+ mockSpanner.unfreeze();
SpannerException e = assertThrows(SpannerException.class, rs::next);
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.RESOURCE_EXHAUSTED);
}
@@ -4989,6 +5060,62 @@ public void testRetryOnResourceExhausted() {
}
}
+ @Test
+ public void testSessionPoolExhaustedError_containsStackTraces() {
+ try (Spanner spanner =
+ SpannerOptions.newBuilder()
+ .setProjectId(TEST_PROJECT)
+ .setChannelProvider(channelProvider)
+ .setCredentials(NoCredentials.getInstance())
+ .setSessionPoolOption(
+ SessionPoolOptions.newBuilder()
+ .setFailIfPoolExhausted()
+ .setMinSessions(2)
+ .setMaxSessions(4)
+ .setWaitForMinSessions(Duration.ofSeconds(10L))
+ .build())
+ .build()
+ .getService()) {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ List transactions = new ArrayList<>();
+ // Deliberately leak 4 sessions.
+ for (int i = 0; i < 4; i++) {
+ // Get a transaction manager without doing anything with it. This will reserve a session
+ // from
+ // the pool, but not increase the number of sessions marked as in use.
+ transactions.add(client.transactionManager());
+ }
+ // Trying to get yet another transaction will fail.
+ // NOTE: This fails directly, because we have set the setFailIfPoolExhausted() option.
+ SpannerException spannerException =
+ assertThrows(SpannerException.class, client::transactionManager);
+ assertEquals(ErrorCode.RESOURCE_EXHAUSTED, spannerException.getErrorCode());
+ assertTrue(
+ spannerException.getMessage(),
+ spannerException.getMessage().contains("There are currently 4 sessions checked out:"));
+ assertTrue(
+ spannerException.getMessage(),
+ spannerException.getMessage().contains("Session was checked out from the pool at"));
+
+ SessionPool pool = ((DatabaseClientImpl) client).pool;
+ // Verify that there are no sessions in the pool.
+ assertEquals(0, pool.getNumberOfSessionsInPool());
+ // Verify that the sessions have not (yet) been marked as in use.
+ assertEquals(0, pool.getNumberOfSessionsInUse());
+ assertEquals(0, pool.getMaxSessionsInUse());
+ // Verify that we have 4 sessions in the pool.
+ assertEquals(4, pool.getTotalSessionsPlusNumSessionsBeingCreated());
+
+ // Release the sessions back into the pool.
+ for (TransactionManager transaction : transactions) {
+ transaction.close();
+ }
+ // Closing the transactions should return the sessions to the pool.
+ assertEquals(4, pool.getNumberOfSessionsInPool());
+ }
+ }
+
static void assertAsString(String expected, ResultSet resultSet, int col) {
assertEquals(expected, resultSet.getValue(col).getAsString());
assertEquals(ImmutableList.of(expected), resultSet.getValue(col).getAsStringList());
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
index 69004a4913b..b71771ae2ca 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
@@ -46,7 +46,8 @@ private static class SpannerWithClosedSessionsImpl extends SpannerImpl {
}
@Override
- DatabaseClientImpl createDatabaseClient(String clientId, SessionPool pool) {
+ DatabaseClientImpl createDatabaseClient(
+ String clientId, SessionPool pool, MultiplexedSessionDatabaseClient ignore) {
return new DatabaseClientWithClosedSessionImpl(clientId, pool, tracer);
}
}
@@ -89,7 +90,7 @@ PooledSessionFuture getSession() {
@Override
SessionFutureWrapper getMultiplexedSession() {
- SessionFutureWrapper session = super.getMultiplexedSession();
+ SessionFutureWrapper session = (SessionFutureWrapper) super.getMultiplexedSession();
if (invalidateNextSession) {
session.get().get().getDelegate().close();
session.get().get().setAllowReplacing(false);
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/JavaVersionUtil.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/JavaVersionUtil.java
index acca12118c2..99f7a993234 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/JavaVersionUtil.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/JavaVersionUtil.java
@@ -16,20 +16,13 @@
package com.google.cloud.spanner;
+import com.google.gson.internal.JavaVersion;
+
/** Util class for getting the Java version the tests are executed on. */
public class JavaVersionUtil {
/** Returns the major Java version (e.g. 8, 11, 17) */
public static int getJavaMajorVersion() {
- String version = System.getProperty("java.version");
- if (version.startsWith("1.")) {
- version = version.substring(2, 3);
- } else {
- int dot = version.indexOf(".");
- if (dot != -1) {
- version = version.substring(0, dot);
- }
- }
- return Integer.parseInt(version);
+ return JavaVersion.getMajorJavaVersion();
}
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
index 07e8bdae1bc..54b992b69ff 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
@@ -2162,7 +2162,7 @@ public List getRequestsOfType(Class type) {
return result;
}
- public Iterable> getRequestTypes() {
+ public List> getRequestTypes() {
List> res = new LinkedList<>();
for (AbstractMessage m : this.requests) {
res.add(m.getClass());
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java
new file mode 100644
index 00000000000..bf4a02a10c5
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2024 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.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.NoCredentials;
+import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime;
+import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.cloud.spanner.connection.RandomResultSetGenerator;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.spanner.v1.ExecuteSqlRequest;
+import com.google.spanner.v1.Session;
+import io.grpc.Status;
+import java.time.Duration;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiplexedSessionDatabaseClientMockServerTest extends AbstractMockServerTest {
+ private static final Statement STATEMENT = Statement.of("select * from random");
+
+ @BeforeClass
+ public static void setupResults() {
+ mockSpanner.putStatementResults(
+ StatementResult.query(STATEMENT, new RandomResultSetGenerator(1).generate()));
+ }
+
+ @Before
+ public void createSpannerInstance() {
+ spanner =
+ SpannerOptions.newBuilder()
+ .setProjectId("test-project")
+ .setChannelProvider(channelProvider)
+ .setCredentials(NoCredentials.getInstance())
+ .setSessionPoolOption(
+ SessionPoolOptions.newBuilder()
+ .setUseMultiplexedSession(true)
+ // Set the maintainer to loop once every 1ms
+ .setMultiplexedSessionMaintenanceLoopFrequency(Duration.ofMillis(1L))
+ // Set multiplexed sessions to be replaced once every 1ms
+ .setMultiplexedSessionMaintenanceDuration(org.threeten.bp.Duration.ofMillis(1L))
+ .setFailOnSessionLeak()
+ .build())
+ .build()
+ .getService();
+ }
+
+ @Test
+ public void testMultiUseReadOnlyTransactionUsesSameSession() {
+ // Execute two queries using the same transaction. Both queries should use the same
+ // session, also when the maintainer has executed in the meantime.
+ DatabaseClientImpl client =
+ (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
+ try (ReadOnlyTransaction transaction = client.readOnlyTransaction()) {
+ try (ResultSet resultSet = transaction.executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+
+ // Wait until the maintainer has replaced the current session.
+ waitForSessionToBeReplaced(client);
+
+ try (ResultSet resultSet = transaction.executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ }
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+ assertEquals(2, requests.size());
+ assertEquals(requests.get(0).getSession(), requests.get(1).getSession());
+
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+
+ @Test
+ public void testNewTransactionUsesNewSession() {
+ // Execute a single-use read-only transactions, then wait for the maintainer to replace the
+ // current session, and then run another single-use read-only transaction. The two transactions
+ // should use two different sessions.
+ DatabaseClientImpl client =
+ (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+
+ // Wait until the maintainer has replaced the current session.
+ waitForSessionToBeReplaced(client);
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+ assertEquals(2, requests.size());
+ assertNotEquals(requests.get(0).getSession(), requests.get(1).getSession());
+
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(2L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(2L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+
+ @Test
+ public void testMaintainerMaintainsMultipleClients() {
+ // Verify that the single-threaded shared executor that is used by the multiplexed client
+ // maintains and replaces sessions from multiple clients.
+ DatabaseClientImpl client1 =
+ (DatabaseClientImpl)
+ spanner.getDatabaseClient(DatabaseId.of("p", "i", "d" + UUID.randomUUID()));
+ DatabaseClientImpl client2 =
+ (DatabaseClientImpl)
+ spanner.getDatabaseClient(DatabaseId.of("p", "i", "d" + UUID.randomUUID()));
+
+ for (DatabaseClientImpl client : ImmutableList.of(client1, client2)) {
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ // Wait until the maintainer has replaced the current session.
+ waitForSessionToBeReplaced(client);
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ }
+
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+ assertEquals(4, requests.size());
+ // Put all session IDs in a Set to verify that they were all different.
+ Set sessionIds =
+ requests.stream().map(ExecuteSqlRequest::getSession).collect(Collectors.toSet());
+ assertEquals(4, sessionIds.size());
+
+ for (DatabaseClientImpl client : ImmutableList.of(client1, client2)) {
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(2L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(2L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+ }
+
+ @Test
+ public void testUnimplementedErrorOnCreation_fallsBackToRegularSessions() {
+ mockSpanner.setCreateSessionExecutionTime(
+ SimulatedExecutionTime.ofException(
+ Status.UNIMPLEMENTED
+ .withDescription("Multiplexed sessions are not implemented")
+ .asRuntimeException()));
+ DatabaseClientImpl client =
+ (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
+ // Get the current session reference. This will block until the CreateSession RPC has failed.
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ SpannerException spannerException =
+ assertThrows(
+ SpannerException.class,
+ client.multiplexedSessionDatabaseClient::getCurrentSessionReference);
+ assertEquals(ErrorCode.UNIMPLEMENTED, spannerException.getErrorCode());
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ // Verify that we received one ExecuteSqlRequest, and that it used a regular session.
+ assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+
+ Session session = mockSpanner.getSession(requests.get(0).getSession());
+ assertNotNull(session);
+ assertFalse(session.getMultiplexed());
+
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(0L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(0L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+
+ @Test
+ public void
+ testUnimplementedErrorOnCreation_firstReceivesError_secondFallsBackToRegularSessions() {
+ mockSpanner.setCreateSessionExecutionTime(
+ SimulatedExecutionTime.ofException(
+ Status.UNIMPLEMENTED
+ .withDescription("Multiplexed sessions are not implemented")
+ .asRuntimeException()));
+ // Freeze the mock server to ensure that the CreateSession RPC does not return an error or any
+ // other result just yet.
+ mockSpanner.freeze();
+ // Get a database client using multiplexed sessions. The CreateSession RPC will be blocked as
+ // long as the mock server is frozen.
+ DatabaseClientImpl client =
+ (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
+ // Try to execute a query. This is all non-blocking until the call to ResultSet#next().
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ // Unfreeze the mock server to get the error from the backend. This query will then fail.
+ mockSpanner.unfreeze();
+ SpannerException spannerException = assertThrows(SpannerException.class, resultSet::next);
+ assertEquals(ErrorCode.UNIMPLEMENTED, spannerException.getErrorCode());
+ }
+ // The next query will fall back to regular sessions and succeed.
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ // Verify that we received one ExecuteSqlRequest, and that it used a regular session.
+ assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+
+ Session session = mockSpanner.getSession(requests.get(0).getSession());
+ assertNotNull(session);
+ assertFalse(session.getMultiplexed());
+
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(0L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(0L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+
+ @Test
+ public void testMaintainerInvalidatesMultiplexedSessionClientIfUnimplemented() {
+ DatabaseClientImpl client =
+ (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
+ // The first query should succeed.
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ // Now ensure that CreateSession returns UNIMPLEMENTED. This error should be recognized by the
+ // maintainer and invalidate the MultiplexedSessionDatabaseClient. New queries will fall back to
+ // regular sessions.
+ mockSpanner.setCreateSessionExecutionTime(
+ SimulatedExecutionTime.ofException(
+ Status.UNIMPLEMENTED
+ .withDescription("Multiplexed sessions are not implemented")
+ .asRuntimeException()));
+ // Wait until the client sees that MultiplexedSessions are not supported.
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ while (client.multiplexedSessionDatabaseClient.isMultiplexedSessionsSupported()
+ && stopwatch.elapsed().compareTo(Duration.ofSeconds(5)) < 0) {
+ Thread.yield();
+ }
+ // Queries should fall back to regular sessions.
+ try (ResultSet resultSet = client.singleUse().executeQuery(STATEMENT)) {
+ //noinspection StatementWithEmptyBody
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ // Verify that we received two ExecuteSqlRequests, and that the first one used a multiplexed
+ // session, and that the second used a regular session.
+ assertEquals(2, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+
+ Session session1 = mockSpanner.getSession(requests.get(0).getSession());
+ assertNotNull(session1);
+ assertTrue(session1.getMultiplexed());
+
+ Session session2 = mockSpanner.getSession(requests.get(1).getSession());
+ assertNotNull(session2);
+ assertFalse(session2.getMultiplexed());
+
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get());
+ assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
+ }
+
+ private void waitForSessionToBeReplaced(DatabaseClientImpl client) {
+ assertNotNull(client.multiplexedSessionDatabaseClient);
+ SessionReference sessionReference =
+ client.multiplexedSessionDatabaseClient.getCurrentSessionReference();
+ while (sessionReference
+ == client.multiplexedSessionDatabaseClient.getCurrentSessionReference()) {
+ Thread.yield();
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientTest.java
new file mode 100644
index 00000000000..d7d9b7395ed
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 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.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.cloud.spanner.SessionClient.SessionConsumer;
+import java.lang.reflect.Field;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.stubbing.Answer;
+
+@RunWith(JUnit4.class)
+public class MultiplexedSessionDatabaseClientTest {
+
+ @Test
+ public void testMaintainer() {
+ // This fails for the native builds due to the extensive use of reflection.
+ assumeTrue(isJava8());
+
+ Instant now = Instant.now();
+ Clock clock = mock(Clock.class);
+ when(clock.instant()).thenReturn(now);
+ SessionClient sessionClient = mock(SessionClient.class);
+ SpannerImpl spanner = mock(SpannerImpl.class);
+ SpannerOptions spannerOptions = mock(SpannerOptions.class);
+ SessionPoolOptions sessionPoolOptions = mock(SessionPoolOptions.class);
+ when(sessionClient.getSpanner()).thenReturn(spanner);
+ when(spanner.getOptions()).thenReturn(spannerOptions);
+ when(spannerOptions.getSessionPoolOptions()).thenReturn(sessionPoolOptions);
+ when(sessionPoolOptions.getMultiplexedSessionMaintenanceDuration())
+ .thenReturn(org.threeten.bp.Duration.ofDays(7));
+ when(sessionPoolOptions.getMultiplexedSessionMaintenanceLoopFrequency())
+ .thenReturn(Duration.ofMinutes(10));
+
+ SessionImpl session1 = mock(SessionImpl.class);
+ SessionReference sessionReference1 = mock(SessionReference.class);
+ when(session1.getSessionReference()).thenReturn(sessionReference1);
+
+ SessionImpl session2 = mock(SessionImpl.class);
+ SessionReference sessionReference2 = mock(SessionReference.class);
+ when(session2.getSessionReference()).thenReturn(sessionReference2);
+
+ doAnswer(
+ (Answer>)
+ invocationOnMock -> {
+ SessionConsumer consumer = invocationOnMock.getArgument(0);
+ // Return session1 the first time it is called.
+ consumer.onSessionReady(session1);
+ return null;
+ })
+ .doAnswer(
+ (Answer>)
+ invocationOnMock -> {
+ SessionConsumer consumer = invocationOnMock.getArgument(0);
+ // Return session2 the second time that it is called.
+ consumer.onSessionReady(session2);
+ return null;
+ })
+ .when(sessionClient)
+ .asyncCreateMultiplexedSession(any(SessionConsumer.class));
+
+ // Create a client. This should get session1.
+ MultiplexedSessionDatabaseClient client =
+ new MultiplexedSessionDatabaseClient(sessionClient, clock);
+
+ // Make sure that the client uses the initial session that is created.
+ assertEquals(client.getCurrentSessionReference(), session1.getSessionReference());
+
+ // Run the maintainer without advancing the clock. We should still get the same session.
+ client.getMaintainer().maintain();
+ assertEquals(client.getCurrentSessionReference(), session1.getSessionReference());
+
+ // Advance the clock by 1 day. We should still get the same session.
+ when(clock.instant()).thenReturn(now.plus(Duration.ofDays(1)));
+ client.getMaintainer().maintain();
+ assertEquals(client.getCurrentSessionReference(), session1.getSessionReference());
+
+ // Advance the clock by 8 days. We should now get a new session.
+ when(clock.instant()).thenReturn(now.plus(Duration.ofDays(8)));
+ client.getMaintainer().maintain();
+ assertEquals(client.getCurrentSessionReference(), session2.getSessionReference());
+ }
+
+ @Test
+ public void testForceDisableEnvVar() throws Exception {
+ assumeTrue(isJava8() && !isWindows());
+ assumeFalse(
+ System.getenv().containsKey("GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS"));
+
+ // Assert that the mux sessions setting is respected by default.
+ assertTrue(
+ SessionPoolOptions.newBuilder()
+ .setUseMultiplexedSession(true)
+ .build()
+ .getUseMultiplexedSession());
+
+ Class> classOfMap = System.getenv().getClass();
+ Field field = classOfMap.getDeclaredField("m");
+ field.setAccessible(true);
+ Map writeableEnvironmentVariables =
+ (Map) field.get(System.getenv());
+
+ try {
+ writeableEnvironmentVariables.put(
+ "GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS", "true");
+ // Assert that the env var overrides the mux sessions setting.
+ assertFalse(
+ SessionPoolOptions.newBuilder()
+ .setUseMultiplexedSession(true)
+ .build()
+ .getUseMultiplexedSession());
+ } finally {
+ writeableEnvironmentVariables.remove(
+ "GOOGLE_CLOUD_SPANNER_FORCE_DISABLE_MULTIPLEXED_SESSIONS");
+ }
+ }
+
+ private boolean isJava8() {
+ return JavaVersionUtil.getJavaMajorVersion() == 8;
+ }
+
+ private boolean isWindows() {
+ return System.getProperty("os.name").toLowerCase().contains("windows");
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionMaintainerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionMaintainerTest.java
index 457004a18fb..f596183507e 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionMaintainerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionMaintainerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -41,8 +42,8 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
-import org.junit.Assume;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -62,6 +63,11 @@ public class MultiplexedSessionMaintainerTest extends BaseSessionPoolTest {
private FakeClock clock = new FakeClock();
private List multiplexedSessionsRemoved = new ArrayList<>();
+ @BeforeClass
+ public static void checkUsesMultiplexedSessionPool() {
+ assumeTrue("Only run if the maintainer in the session pool is used", false);
+ }
+
@Before
public void setUp() {
initMocks(this);
@@ -81,7 +87,7 @@ public void setUp() {
.setPoolMaintainerClock(clock)
.build();
when(spannerOptions.getSessionPoolOptions()).thenReturn(options);
- Assume.assumeTrue(options.getUseMultiplexedSession());
+ assumeTrue(options.getUseMultiplexedSession());
multiplexedSessionsRemoved.clear();
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionPoolTest.java
index 5e96e519fc7..ed9926dea88 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionPoolTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionPoolTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
@@ -36,8 +37,8 @@
import io.opentelemetry.api.OpenTelemetry;
import java.io.PrintWriter;
import java.io.StringWriter;
-import org.junit.Assume;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mock;
import org.threeten.bp.Duration;
@@ -66,6 +67,11 @@ private SessionPool createPool() {
OpenTelemetry.noop());
}
+ @BeforeClass
+ public static void checkUsesMultiplexedSessionPool() {
+ assumeTrue("Only run if the maintainer in the session pool is used", false);
+ }
+
@Before
public void setUp() {
initMocks(this);
@@ -83,7 +89,7 @@ public void setUp() {
.setUseMultiplexedSession(true)
.build();
when(spannerOptions.getSessionPoolOptions()).thenReturn(options);
- Assume.assumeTrue(options.getUseMultiplexedSession());
+ assumeTrue(options.getUseMultiplexedSession());
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java
index 8a4859fa132..a351231f0e7 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java
@@ -22,6 +22,7 @@
import com.google.api.gax.grpc.testing.LocalChannelProvider;
import com.google.cloud.NoCredentials;
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ListValue;
import com.google.spanner.v1.ResultSetMetadata;
@@ -231,7 +232,7 @@ public void setUp() throws Exception {
.setSessionPoolOption(
SessionPoolOptions.newBuilder()
.setMinSessions(2)
- .setWaitForMinSessions(Duration.ofSeconds(5))
+ .setWaitForMinSessions(Duration.ofSeconds(10))
.build());
spanner = builder.build().getService();
@@ -250,9 +251,7 @@ public void tearDown() {
@Test
public void singleUse() {
List expectedReadOnlyTransactionSingleUseEvents =
- isMultiplexedSessionsEnabled()
- ? ImmutableList.of("Using Session")
- : ImmutableList.of("Acquiring session", "Acquired session", "Using Session");
+ getExpectedReadOnlyTransactionSingleUseEvents();
List expectedReadOnlyTransactionSpans =
isMultiplexedSessionsEnabled()
? ImmutableList.of(
@@ -266,7 +265,8 @@ public void singleUse() {
"CloudSpannerOperation.ExecuteStreamingQuery",
"CloudSpannerOperation.BatchCreateSessions",
"CloudSpanner.ReadOnlyTransaction");
- int expectedReadOnlyTransactionSingleUseEventsCount = isMultiplexedSessionsEnabled() ? 1 : 3;
+ int expectedReadOnlyTransactionSingleUseEventsCount =
+ expectedReadOnlyTransactionSingleUseEvents.size();
try (ResultSet rs = client.singleUse().executeQuery(SELECT1)) {
while (rs.next()) {
@@ -322,6 +322,17 @@ public void singleUse() {
verifySpans(actualSpanItems, expectedReadOnlyTransactionSpans);
}
+ private List getExpectedReadOnlyTransactionSingleUseEvents() {
+ List expectedReadOnlyTransactionSingleUseEvents;
+ if (isMultiplexedSessionsEnabled()) {
+ expectedReadOnlyTransactionSingleUseEvents = ImmutableList.of();
+ } else {
+ expectedReadOnlyTransactionSingleUseEvents =
+ ImmutableList.of("Acquiring session", "Acquired session", "Using Session");
+ }
+ return expectedReadOnlyTransactionSingleUseEvents;
+ }
+
@Test
public void multiUse() {
List expectedReadOnlyTransactionSpans =
@@ -337,17 +348,21 @@ public void multiUse() {
"CloudSpannerOperation.ExecuteStreamingQuery",
"CloudSpannerOperation.BatchCreateSessions",
"CloudSpanner.ReadOnlyTransaction");
- List expectedReadOnlyTransactionMultiUseEvents =
- isMultiplexedSessionsEnabled()
- ? ImmutableList.of("Using Session", "Creating Transaction", "Transaction Creation Done")
- : ImmutableList.of(
- "Acquiring session",
- "Acquired session",
- "Using Session",
- "Creating Transaction",
- "Transaction Creation Done");
-
- int expectedReadOnlyTransactionMultiUseEventsCount = isMultiplexedSessionsEnabled() ? 3 : 5;
+ List expectedReadOnlyTransactionMultiUseEvents;
+ if (isMultiplexedSessionsEnabled()) {
+ expectedReadOnlyTransactionMultiUseEvents =
+ ImmutableList.of("Creating Transaction", "Transaction Creation Done");
+ } else {
+ expectedReadOnlyTransactionMultiUseEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "Acquired session",
+ "Using Session",
+ "Creating Transaction",
+ "Transaction Creation Done");
+ }
+ int expectedReadOnlyTransactionMultiUseEventsCount =
+ expectedReadOnlyTransactionMultiUseEvents.size();
try (ReadOnlyTransaction tx = client.readOnlyTransaction()) {
try (ResultSet rs = tx.executeQuery(SELECT1)) {
@@ -537,7 +552,8 @@ public void transactionRunnerWithFailedAndBeginTransaction() {
"CloudSpannerOperation.Commit",
"CloudSpannerOperation.BatchCreateSessions",
"CloudSpanner.ReadWriteTransaction");
- Long updateCount =
+ assertEquals(
+ Long.valueOf(1L),
client
.readWriteTransaction()
.run(
@@ -552,7 +568,16 @@ public void transactionRunnerWithFailedAndBeginTransaction() {
() -> transaction.executeUpdate(INVALID_UPDATE_STATEMENT));
assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
return transaction.executeUpdate(UPDATE_STATEMENT);
- });
+ }));
+ // Wait for all spans to finish. Failing to do so can cause the test to miss the
+ // BatchCreateSessions span, as that span is executed asynchronously in the SessionClient, and
+ // the SessionClient returns the session to the pool before the span has finished fully.
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ while (spanExporter.getFinishedSpanItems().size()
+ < expectedReadWriteTransactionWithCommitAndBeginTransactionSpans.size()
+ && stopwatch.elapsed().compareTo(java.time.Duration.ofMillis(1000)) < 0) {
+ Thread.yield();
+ }
List actualSpanItems = new ArrayList<>();
spanExporter
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java
index 13dd8500b76..dbef7ce29f6 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java
@@ -184,17 +184,19 @@ public void pointReadNotFound() throws Exception {
}
@Test
- public void invalidDatabase() throws Exception {
+ public void invalidDatabase() {
mockSpanner.setCreateSessionExecutionTime(
SimulatedExecutionTime.stickyDatabaseNotFoundException("invalid-database"));
mockSpanner.setBatchCreateSessionsExecutionTime(
SimulatedExecutionTime.stickyDatabaseNotFoundException("invalid-database"));
+ mockSpanner.freeze();
DatabaseClient invalidClient =
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, "invalid-database"));
ApiFuture row =
invalidClient
.singleUse(TimestampBound.strong())
.readRowAsync(READ_TABLE_NAME, Key.of("k99"), READ_COLUMN_NAMES);
+ mockSpanner.unfreeze();
assertThrows(DatabaseNotFoundException.class, () -> get(row));
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java
index 75580c25124..ab7eb80cf90 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java
@@ -117,6 +117,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -192,7 +193,9 @@ private SessionPool createPool(
tracer,
labelValues,
OpenTelemetry.noop(),
- null);
+ null,
+ new AtomicLong(),
+ new AtomicLong());
}
private SessionPool createPool(
@@ -212,7 +215,9 @@ private SessionPool createPool(
tracer,
labelValues,
openTelemetry,
- attributes);
+ attributes,
+ new AtomicLong(),
+ new AtomicLong());
}
@BeforeClass
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java
index 9a13731b0f7..fdfe4871680 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java
@@ -281,6 +281,21 @@ public void singleUse() {
// Just consume the result set.
}
}
+ verifySingleUseSpans();
+ }
+
+ @Test
+ public void singleUseWithoutTryWithResources() {
+ // NOTE: This is bad practice. Always use try-with-resources. This test is only here to verify
+ // that our safeguards work.
+ ResultSet rs = client.singleUse().executeQuery(SELECT1);
+ while (rs.next()) {
+ // Just consume the result set.
+ }
+ verifySingleUseSpans();
+ }
+
+ private void verifySingleUseSpans() {
// OpenTelemetry spans should be 0 as OpenCensus is default enabled.
assertEquals(openTelemetrySpanExporter.getFinishedSpanItems().size(), 0);
@@ -307,7 +322,6 @@ public void singleUse() {
"Request for 2 sessions returned 2 sessions",
"Request for 1 multiplexed session returned 1 session",
"Creating 2 sessions",
- "Using Session",
"Starting/Resuming stream");
if (spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()) {
verifyAnnotations(
@@ -324,6 +338,61 @@ public void singleUse() {
}
}
+ @Test
+ public void singleUseWithError() {
+ Statement invalidStatement = Statement.of("select * from foo");
+ mockSpanner.putStatementResults(
+ StatementResult.exception(invalidStatement, Status.INVALID_ARGUMENT.asRuntimeException()));
+
+ SpannerException spannerException =
+ assertThrows(
+ SpannerException.class,
+ () -> client.singleUse().executeQuery(INVALID_UPDATE_STATEMENT).next());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, spannerException.getErrorCode());
+
+ // OpenTelemetry spans should be 0 as OpenCensus is default enabled.
+ assertEquals(openTelemetrySpanExporter.getFinishedSpanItems().size(), 0);
+
+ // OpenCensus spans and events verification
+ Map spans = failOnOverkillTraceComponent.getSpans();
+ assertThat(spans).containsEntry("CloudSpanner.ReadOnlyTransaction", true);
+ assertThat(spans).containsEntry("CloudSpannerOperation.BatchCreateSessions", true);
+ assertThat(spans).containsEntry("CloudSpannerOperation.BatchCreateSessionsRequest", true);
+ assertThat(spans).containsEntry("CloudSpannerOperation.ExecuteStreamingQuery", true);
+
+ List expectedAnnotations =
+ ImmutableList.of(
+ "Requesting 2 sessions",
+ "Request for 2 sessions returned 2 sessions",
+ "Creating 2 sessions",
+ "Acquiring session",
+ "Acquired session",
+ "Using Session",
+ "Starting/Resuming stream",
+ "Stream broken. Not safe to retry");
+ List expectedAnnotationsForMultiplexedSession =
+ ImmutableList.of(
+ "Requesting 2 sessions",
+ "Request for 2 sessions returned 2 sessions",
+ "Request for 1 multiplexed session returned 1 session",
+ "Creating 2 sessions",
+ "Starting/Resuming stream",
+ "Stream broken. Not safe to retry");
+ if (spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession()) {
+ verifyAnnotations(
+ failOnOverkillTraceComponent.getAnnotations().stream()
+ .distinct()
+ .collect(Collectors.toList()),
+ expectedAnnotationsForMultiplexedSession);
+ } else {
+ verifyAnnotations(
+ failOnOverkillTraceComponent.getAnnotations().stream()
+ .distinct()
+ .collect(Collectors.toList()),
+ expectedAnnotations);
+ }
+ }
+
@Test
public void multiUse() {
try (ReadOnlyTransaction tx = client.readOnlyTransaction()) {
@@ -357,7 +426,6 @@ public void multiUse() {
"Request for 2 sessions returned 2 sessions",
"Request for 1 multiplexed session returned 1 session",
"Creating 2 sessions",
- "Using Session",
"Starting/Resuming stream",
"Creating Transaction",
"Transaction Creation Done");
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java
index 8c5b9be5e07..90d76c6a2bb 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java
@@ -284,7 +284,8 @@ public void singleUseResourceExhausted_retryable() {
@Test
public void singleUseNonRetryableError() {
- mockSpanner.addException(FAILED_PRECONDITION);
+ mockSpanner.setExecuteStreamingSqlExecutionTime(
+ SimulatedExecutionTime.ofException(FAILED_PRECONDITION));
try (ResultSet rs = client.singleUse().executeQuery(SELECT1AND2)) {
SpannerException e = assertThrows(SpannerException.class, () -> rs.next());
assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode());
@@ -294,7 +295,8 @@ public void singleUseNonRetryableError() {
@Test
public void singleUseNonRetryableErrorOnNext() {
try (ResultSet rs = client.singleUse().executeQuery(SELECT1AND2)) {
- mockSpanner.addException(FAILED_PRECONDITION);
+ mockSpanner.setExecuteStreamingSqlExecutionTime(
+ SimulatedExecutionTime.ofException(FAILED_PRECONDITION));
SpannerException e = assertThrows(SpannerException.class, () -> rs.next());
assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode());
}
@@ -302,7 +304,8 @@ public void singleUseNonRetryableErrorOnNext() {
@Test
public void singleUseInternal() {
- mockSpanner.addException(new IllegalArgumentException());
+ mockSpanner.setExecuteStreamingSqlExecutionTime(
+ SimulatedExecutionTime.ofException(new IllegalArgumentException()));
try (ResultSet rs = client.singleUse().executeQuery(SELECT1AND2)) {
SpannerException e = assertThrows(SpannerException.class, () -> rs.next());
assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java
index 015927440d5..7b57e3f9014 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java
@@ -1751,7 +1751,7 @@ public void testTransactionTagNotAllowedAfterTransactionStarted() {
new ConnectionImpl(
connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) {
@Override
- UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
+ UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery, boolean forceSingleUse) {
return unitOfWork;
}
}) {
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTransactionalReadWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTransactionalReadWriteTest.java
index aac7497d074..ba47421287a 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTransactionalReadWriteTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTransactionalReadWriteTest.java
@@ -151,7 +151,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -765,7 +766,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -920,7 +922,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -1074,7 +1077,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -1378,7 +1382,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -1829,7 +1834,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
@@ -1979,7 +1985,8 @@ boolean isGetCommitTimestampAllowed() {
boolean isExecuteAllowed(StatementType type) {
return type == StatementType.CLIENT_SIDE
|| type == StatementType.QUERY
- || type == StatementType.UPDATE;
+ || type == StatementType.UPDATE
+ || type == StatementType.DDL;
}
@Override
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java
index d88882fc4c2..c52be0e4d09 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java
@@ -290,7 +290,10 @@ public void testMaxSessions()
assertThat(count1.isDone()).isTrue();
assertThat(count2.isDone()).isTrue();
if (isMultiplexedSessionsEnabled(connection1.getSpanner())) {
- assertThat(mockSpanner.numSessionsCreated()).isEqualTo(2);
+ // We don't use the multiplexed session, so we don't know whether the server had time to
+ // create it or not. That means that we have between 1 and 2 sessions on the server.
+ assertThat(mockSpanner.numSessionsCreated()).isAtLeast(1);
+ assertThat(mockSpanner.numSessionsCreated()).isAtMost(2);
} else {
assertThat(mockSpanner.numSessionsCreated()).isEqualTo(1);
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlTest.java
index 0b9b2bdd8dd..44a2f4d9ff7 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlTest.java
@@ -17,7 +17,10 @@
package com.google.cloud.spanner.connection;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.StatementResult.ResultType;
import com.google.longrunning.Operation;
@@ -25,6 +28,7 @@
import com.google.protobuf.Empty;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest;
+import com.google.spanner.v1.CommitRequest;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.After;
@@ -106,4 +110,124 @@ public void testBatchedAnalyzeStatement() {
assertEquals("create table foo (id int64) primary key (id)", requests.get(0).getStatements(0));
assertEquals("analyze", requests.get(0).getStatements(1));
}
+
+ @Test
+ public void testDdlAtStartOfTransaction() {
+ Statement statement = Statement.of("create table foo (id int64) primary key (id)");
+ for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
+ mockDatabaseAdmin.getRequests().clear();
+ if (mode != DdlInTransactionMode.FAIL) {
+ addUpdateDdlResponse();
+ }
+
+ try (Connection connection = createConnection()) {
+ connection.setAutocommit(false);
+ connection.setDdlInTransactionMode(mode);
+
+ if (mode == DdlInTransactionMode.FAIL) {
+ SpannerException exception =
+ assertThrows(SpannerException.class, () -> connection.execute(statement));
+ assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
+ } else {
+ assertEquals(ResultType.NO_RESULT, connection.execute(statement).getResultType());
+ assertEquals(1, mockDatabaseAdmin.getRequests().size());
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDdlBatchAtStartOfTransaction() {
+ for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
+ mockDatabaseAdmin.getRequests().clear();
+ if (mode != DdlInTransactionMode.FAIL) {
+ addUpdateDdlResponse();
+ }
+
+ try (Connection connection = createConnection()) {
+ connection.setAutocommit(false);
+ connection.setDdlInTransactionMode(mode);
+
+ if (mode == DdlInTransactionMode.FAIL) {
+ SpannerException exception =
+ assertThrows(
+ SpannerException.class,
+ () -> connection.execute(Statement.of("start batch ddl")));
+ assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
+ } else {
+ connection.execute(Statement.of("start batch ddl"));
+ connection.execute(Statement.of("create table foo"));
+ connection.execute(Statement.of("alter table bar"));
+ connection.execute(Statement.of("run batch"));
+ assertEquals(1, mockDatabaseAdmin.getRequests().size());
+ UpdateDatabaseDdlRequest request =
+ (UpdateDatabaseDdlRequest) mockDatabaseAdmin.getRequests().get(0);
+ assertEquals(2, request.getStatementsCount());
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDdlInTransaction() {
+ Statement statement = Statement.of("create table foo (id int64) primary key (id)");
+ for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
+ mockDatabaseAdmin.getRequests().clear();
+ if (mode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
+ addUpdateDdlResponse();
+ }
+
+ try (Connection connection = createConnection()) {
+ connection.setAutocommit(false);
+ connection.setDdlInTransactionMode(mode);
+
+ connection.execute(INSERT_STATEMENT);
+
+ if (mode != DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
+ SpannerException exception =
+ assertThrows(SpannerException.class, () -> connection.execute(statement));
+ assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
+ } else {
+ assertEquals(ResultType.NO_RESULT, connection.execute(statement).getResultType());
+ assertEquals(1, mockDatabaseAdmin.getRequests().size());
+ assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDdlBatchInTransaction() {
+ for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
+ mockDatabaseAdmin.getRequests().clear();
+ if (mode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
+ addUpdateDdlResponse();
+ }
+
+ try (Connection connection = createConnection()) {
+ connection.setAutocommit(false);
+ connection.setDdlInTransactionMode(mode);
+
+ connection.execute(INSERT_STATEMENT);
+
+ if (mode != DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
+ SpannerException exception =
+ assertThrows(
+ SpannerException.class,
+ () -> connection.execute(Statement.of("start batch ddl")));
+ assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
+ } else {
+ connection.execute(Statement.of("start batch ddl"));
+ connection.execute(Statement.of("create table foo"));
+ connection.execute(Statement.of("alter table bar"));
+ connection.execute(Statement.of("run batch"));
+ assertEquals(1, mockDatabaseAdmin.getRequests().size());
+ UpdateDatabaseDdlRequest request =
+ (UpdateDatabaseDdlRequest) mockDatabaseAdmin.getRequests().get(0);
+ assertEquals(2, request.getStatementsCount());
+ assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ }
+ }
+ }
+ }
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseTest.java
index ab45eb67179..f9230b8836b 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseTest.java
@@ -36,7 +36,9 @@
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.ParallelIntegrationTest;
import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.SessionNotFoundException;
import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.TransactionContext;
import com.google.cloud.spanner.TransactionRunner.TransactionCallable;
@@ -89,9 +91,10 @@ public void databaseDeletedTest() throws Exception {
.setMaxElapsedTimeMillis(65000)
.setMaxIntervalMillis(5000)
.build();
- DatabaseNotFoundException notFoundException = null;
- long millis = 0L;
+ ResourceNotFoundException notFoundException = null;
+ long millis;
while ((millis = backoff.nextBackOffMillis()) != ExponentialBackOff.STOP) {
+ //noinspection BusyWait
Thread.sleep(millis);
// Queries to this database should eventually return DatabaseNotFoundExceptions.
try (ResultSet rs = client.singleUse().executeQuery(Statement.of("SELECT 1"))) {
@@ -115,13 +118,40 @@ public void databaseDeletedTest() throws Exception {
Collections.emptyList());
Database newDb = op.get();
- // Queries using the same DatabaseClient should still return DatabaseNotFoundExceptions.
- try (ResultSet rs = client.singleUse().executeQuery(Statement.of("SELECT 1"))) {
- rs.next();
- fail("Missing expected DatabaseNotFoundException");
- } catch (DatabaseNotFoundException e) {
- // This is what we expect.
+ // Now try to query using the old session and verify that we also now (eventually) get a
+ // 'Database not found' error.
+ backoff =
+ new ExponentialBackOff.Builder()
+ .setInitialIntervalMillis(1000)
+ .setMaxElapsedTimeMillis(65000)
+ .setMaxIntervalMillis(5000)
+ .build();
+
+ notFoundException = null;
+ while ((millis = backoff.nextBackOffMillis()) != ExponentialBackOff.STOP) {
+ //noinspection BusyWait
+ Thread.sleep(millis);
+ // Queries to this database should eventually return DatabaseNotFoundExceptions.
+ try (ResultSet rs = client.singleUse().executeQuery(Statement.of("SELECT 1"))) {
+ rs.next();
+ } catch (DatabaseNotFoundException databaseNotFoundException) {
+ // This is what we expect.
+ notFoundException = databaseNotFoundException;
+ break;
+ } catch (SessionNotFoundException sessionNotFoundException) {
+ if (isUsingEmulator()) {
+ // This is expected on the emulator, as the emulator does not see a difference between two
+ // different databases with the same name. The original session from the first database is
+ // however not present on the newly created database, which is why we get a
+ // SessionNotFoundException.
+ notFoundException = sessionNotFoundException;
+ break;
+ } else {
+ throw sessionNotFoundException;
+ }
+ }
}
+ assertThat(notFoundException).isNotNull();
// Now get a new DatabaseClient for the database. This should now result in a valid
// DatabaseClient.
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
index b8183a6c944..30aed342903 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
@@ -2727,6 +2727,205 @@ NEW_CONNECTION;
@EXPECT EXCEPTION UNIMPLEMENTED
show variable/-return_commit_stats;
NEW_CONNECTION;
+show variable max_commit_delay;
+NEW_CONNECTION;
+SHOW VARIABLE MAX_COMMIT_DELAY;
+NEW_CONNECTION;
+show variable max_commit_delay;
+NEW_CONNECTION;
+ show variable max_commit_delay;
+NEW_CONNECTION;
+ show variable max_commit_delay;
+NEW_CONNECTION;
+
+
+
+show variable max_commit_delay;
+NEW_CONNECTION;
+show variable max_commit_delay ;
+NEW_CONNECTION;
+show variable max_commit_delay ;
+NEW_CONNECTION;
+show variable max_commit_delay
+
+;
+NEW_CONNECTION;
+show variable max_commit_delay;
+NEW_CONNECTION;
+show variable max_commit_delay;
+NEW_CONNECTION;
+show
+variable
+max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable%max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable_max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable&max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable$max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable@max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable!max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable*max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable(max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay);
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable)max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable+max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-#max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable\max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable?max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-/max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/#max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-show variable max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable max_commit_delay/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/-max_commit_delay;
+NEW_CONNECTION;
update foo set bar=1;
show variable commit_response;
NEW_CONNECTION;
@@ -15371,6 +15570,996 @@ NEW_CONNECTION;
@EXPECT EXCEPTION INVALID_ARGUMENT
set return_commit_stats =/-false;
NEW_CONNECTION;
+set max_commit_delay=null;
+NEW_CONNECTION;
+SET MAX_COMMIT_DELAY=NULL;
+NEW_CONNECTION;
+set max_commit_delay=null;
+NEW_CONNECTION;
+ set max_commit_delay=null;
+NEW_CONNECTION;
+ set max_commit_delay=null;
+NEW_CONNECTION;
+
+
+
+set max_commit_delay=null;
+NEW_CONNECTION;
+set max_commit_delay=null ;
+NEW_CONNECTION;
+set max_commit_delay=null ;
+NEW_CONNECTION;
+set max_commit_delay=null
+
+;
+NEW_CONNECTION;
+set max_commit_delay=null;
+NEW_CONNECTION;
+set max_commit_delay=null;
+NEW_CONNECTION;
+set
+max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null);
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay=null/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-max_commit_delay=null;
+NEW_CONNECTION;
+set max_commit_delay='1s';
+NEW_CONNECTION;
+SET MAX_COMMIT_DELAY='1S';
+NEW_CONNECTION;
+set max_commit_delay='1s';
+NEW_CONNECTION;
+ set max_commit_delay='1s';
+NEW_CONNECTION;
+ set max_commit_delay='1s';
+NEW_CONNECTION;
+
+
+
+set max_commit_delay='1s';
+NEW_CONNECTION;
+set max_commit_delay='1s' ;
+NEW_CONNECTION;
+set max_commit_delay='1s' ;
+NEW_CONNECTION;
+set max_commit_delay='1s'
+
+;
+NEW_CONNECTION;
+set max_commit_delay='1s';
+NEW_CONNECTION;
+set max_commit_delay='1s';
+NEW_CONNECTION;
+set
+max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='1s'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-max_commit_delay='1s';
+NEW_CONNECTION;
+set max_commit_delay='100ms';
+NEW_CONNECTION;
+SET MAX_COMMIT_DELAY='100MS';
+NEW_CONNECTION;
+set max_commit_delay='100ms';
+NEW_CONNECTION;
+ set max_commit_delay='100ms';
+NEW_CONNECTION;
+ set max_commit_delay='100ms';
+NEW_CONNECTION;
+
+
+
+set max_commit_delay='100ms';
+NEW_CONNECTION;
+set max_commit_delay='100ms' ;
+NEW_CONNECTION;
+set max_commit_delay='100ms' ;
+NEW_CONNECTION;
+set max_commit_delay='100ms'
+
+;
+NEW_CONNECTION;
+set max_commit_delay='100ms';
+NEW_CONNECTION;
+set max_commit_delay='100ms';
+NEW_CONNECTION;
+set
+max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='100ms'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-max_commit_delay='100ms';
+NEW_CONNECTION;
+set max_commit_delay='10000us';
+NEW_CONNECTION;
+SET MAX_COMMIT_DELAY='10000US';
+NEW_CONNECTION;
+set max_commit_delay='10000us';
+NEW_CONNECTION;
+ set max_commit_delay='10000us';
+NEW_CONNECTION;
+ set max_commit_delay='10000us';
+NEW_CONNECTION;
+
+
+
+set max_commit_delay='10000us';
+NEW_CONNECTION;
+set max_commit_delay='10000us' ;
+NEW_CONNECTION;
+set max_commit_delay='10000us' ;
+NEW_CONNECTION;
+set max_commit_delay='10000us'
+
+;
+NEW_CONNECTION;
+set max_commit_delay='10000us';
+NEW_CONNECTION;
+set max_commit_delay='10000us';
+NEW_CONNECTION;
+set
+max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set max_commit_delay='10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='10000us'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-max_commit_delay='10000us';
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+SET MAX_COMMIT_DELAY='9223372036854775807NS';
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+ set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+ set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+
+
+
+set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns' ;
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns' ;
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns'
+
+;
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+set
+max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set max_commit_delay='9223372036854775807ns'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-max_commit_delay='9223372036854775807ns';
+NEW_CONNECTION;
set statement_tag='tag1';
NEW_CONNECTION;
SET STATEMENT_TAG='TAG1';
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.sql
index bde12f6662e..84275c3d5c3 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.sql
@@ -160,15 +160,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:15.740000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:15.740000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:32.894000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:32.894000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:15.740000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:32.894000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -510,15 +510,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SET READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:16.217000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:16.217000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.008000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.008000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SET READ_ONLY_STALENESS='EXACT_STALENESS 10s';
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:16.217000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.008000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -950,8 +950,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:16.608000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:16.608000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.111000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.111000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -961,7 +961,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
ROLLBACK;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:16.608000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.111000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -1462,8 +1462,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:17.090000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:17.090000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.217000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.217000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -1473,7 +1473,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:17.090000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.217000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -1876,15 +1876,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:17.518000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:17.518000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.318000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.318000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:17.518000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.318000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2243,14 +2243,14 @@ SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:17.918000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.388000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:17.918000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.388000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2600,13 +2600,13 @@ SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:18.303000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.472000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:18.303000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.472000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2910,14 +2910,14 @@ SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:18.684000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:18.684000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.543000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.543000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:18.684000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.543000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -3114,7 +3114,6 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -3246,15 +3245,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:19.116000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:19.116000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.630000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.630000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:19.116000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.630000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -3486,7 +3485,6 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -3664,8 +3662,8 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:19.521000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:19.521000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.713000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.713000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -3674,7 +3672,7 @@ START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:19.521000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.713000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4083,14 +4081,14 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:19.953000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.778000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:19.953000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.778000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4440,13 +4438,13 @@ SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:20.274000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.833000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:20.274000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.833000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4679,7 +4677,6 @@ SET TRANSACTION READ ONLY;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -4880,8 +4877,8 @@ SET TRANSACTION READ ONLY;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:20.662000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:20.662000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.892000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.892000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -4891,7 +4888,7 @@ SET TRANSACTION READ ONLY;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:20.662000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.892000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5291,15 +5288,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET TRANSACTION READ ONLY;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.051000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.051000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.959000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.959000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET TRANSACTION READ ONLY;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.051000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.959000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5513,7 +5510,6 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -5645,15 +5641,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.343000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.343000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.012000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.012000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET READ_ONLY_STALENESS='EXACT_STALENESS 10s';
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.343000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.012000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5892,7 +5888,6 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -6093,8 +6088,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.630000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.630000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.073000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.073000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -6104,7 +6099,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
ROLLBACK;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.630000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.073000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -6412,7 +6407,6 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -6613,8 +6607,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:22.181000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:22.181000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.152000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.152000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -6624,7 +6618,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:22.181000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.152000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7029,15 +7023,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:22.566000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:22.566000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.223000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.223000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:22.566000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.223000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7400,14 +7394,14 @@ SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:22.853000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.285000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:22.853000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.285000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7762,13 +7756,13 @@ SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.261000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.350000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.261000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.350000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7973,7 +7967,6 @@ UPDATE foo SET bar=1;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -8082,14 +8075,14 @@ SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.615000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:23.615000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.415000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.415000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.615000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.415000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -8399,13 +8392,13 @@ SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.894000000Z';
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.468000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.894000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.468000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -8760,8 +8753,8 @@ SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SET TRANSACTION READ ONLY;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.167000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.167000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.517000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.517000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -8769,7 +8762,7 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SET TRANSACTION READ ONLY;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.167000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.517000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -9204,8 +9197,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
UPDATE foo SET bar=1;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.502000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.502000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.575000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.575000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -9213,8 +9206,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
UPDATE foo SET bar=1;
COMMIT;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.502000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:24.502000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.575000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.575000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -9600,15 +9593,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.834000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.834000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.641000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.641000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.834000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.641000000Z';
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -9959,15 +9952,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.130000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.130000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.691000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.691000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.130000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.130000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.691000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.691000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -10327,15 +10320,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
UPDATE foo SET bar=1;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.446000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.446000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.768000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.768000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
UPDATE foo SET bar=1;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.446000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.446000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.768000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.768000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -10725,16 +10718,16 @@ SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.758000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.758000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.829000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.829000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.758000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.758000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.829000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.829000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -11117,15 +11110,15 @@ NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.062000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.062000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.886000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.886000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.062000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.062000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.886000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.886000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -11455,14 +11448,14 @@ SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.366000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.366000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.946000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.946000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.366000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.366000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.946000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.946000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=FALSE;
@@ -11785,15 +11778,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SET READ_ONLY_STALENESS='MAX_STALENESS 10s';
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.618000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.618000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.999000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.999000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SET READ_ONLY_STALENESS='MAX_STALENESS 10s';
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.618000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.618000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.999000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.999000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -12200,8 +12193,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.883000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.883000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.050000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.050000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -12209,8 +12202,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.883000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.883000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.050000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.050000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -12593,15 +12586,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.234000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.234000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.106000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.106000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.234000000Z';
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.106000000Z';
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
@@ -12939,15 +12932,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.524000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.524000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.157000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.157000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.524000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:27.524000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.157000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.157000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -13294,15 +13287,15 @@ NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.818000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.818000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.213000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.213000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.818000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:27.818000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.213000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.213000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
@@ -13619,14 +13612,14 @@ SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
-SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:28.091000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:28.091000000Z'
+SET READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.269000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.269000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
-SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:28.091000000Z';
-@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:28.091000000Z'
+SET READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.269000000Z';
+@EXPECT RESULT_SET 'READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.269000000Z'
SHOW VARIABLE READ_ONLY_STALENESS;
NEW_CONNECTION;
SET READONLY=TRUE;
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITTransactionModeTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITTransactionModeTest.sql
index 7bb0f80943a..e2253d3cda0 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITTransactionModeTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITTransactionModeTest.sql
@@ -33,6 +33,8 @@ NEW_CONNECTION;
SHOW VARIABLE AUTOCOMMIT;
@EXPECT RESULT_SET 'READONLY',false
SHOW VARIABLE READONLY;
+@EXPECT RESULT_SET 'C',1
+SELECT 1 AS C;
@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE FOO (ID INT64 NOT NULL, NAME STRING(100) PRIMARY KEY (ID);
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ClientSideStatementsTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ClientSideStatementsTest.sql
index e8843ab6aa7..5ffa722385d 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ClientSideStatementsTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ClientSideStatementsTest.sql
@@ -5443,6 +5443,403 @@ NEW_CONNECTION;
@EXPECT EXCEPTION UNIMPLEMENTED
show variable/-spanner.return_commit_stats;
NEW_CONNECTION;
+show spanner.max_commit_delay;
+NEW_CONNECTION;
+SHOW SPANNER.MAX_COMMIT_DELAY;
+NEW_CONNECTION;
+show spanner.max_commit_delay;
+NEW_CONNECTION;
+ show spanner.max_commit_delay;
+NEW_CONNECTION;
+ show spanner.max_commit_delay;
+NEW_CONNECTION;
+
+
+
+show spanner.max_commit_delay;
+NEW_CONNECTION;
+show spanner.max_commit_delay ;
+NEW_CONNECTION;
+show spanner.max_commit_delay ;
+NEW_CONNECTION;
+show spanner.max_commit_delay
+
+;
+NEW_CONNECTION;
+show spanner.max_commit_delay;
+NEW_CONNECTION;
+show spanner.max_commit_delay;
+NEW_CONNECTION;
+show
+spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show%spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show_spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show&spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show$spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show@spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show!spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show*spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show(spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay);
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show)spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show-spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show+spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show-#spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show/spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show\spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show?spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show-/spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show/#spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-show spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show spanner.max_commit_delay/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+show/-spanner.max_commit_delay;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+SHOW VARIABLE SPANNER.MAX_COMMIT_DELAY;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+ show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+ show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+
+
+
+show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay ;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay ;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay
+
+;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+show
+variable
+spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable%spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable_spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable&spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable$spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable@spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable!spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable*spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable(spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay);
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable)spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable+spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-#spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable\spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable?spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-/spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/#spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-show variable spanner.max_commit_delay;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable spanner.max_commit_delay/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/-spanner.max_commit_delay;
+NEW_CONNECTION;
update foo set bar=1;
show spanner.commit_response;
NEW_CONNECTION;
@@ -66128,6 +66525,1000 @@ NEW_CONNECTION;
@EXPECT EXCEPTION INVALID_ARGUMENT
set spanner.return_commit_stats to/-false;
NEW_CONNECTION;
+set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+SET SPANNER.MAX_COMMIT_DELAY=NULL;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+ set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+ set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+
+
+
+set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null ;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null ;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null
+
+;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+set
+spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null);
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.max_commit_delay=null;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay=null/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-spanner.max_commit_delay=null;
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+SET SPANNER.MAX_COMMIT_DELAY='1S';
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+ set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+ set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+
+
+
+set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s'
+
+;
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+set
+spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='1s'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-spanner.max_commit_delay='1s';
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+SET SPANNER.MAX_COMMIT_DELAY='100MS';
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+ set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+ set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+
+
+
+set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms'
+
+;
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+set
+spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay='100ms'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-spanner.max_commit_delay='100ms';
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+SET SPANNER.MAX_COMMIT_DELAY TO '10000US';
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+ set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+ set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+
+
+
+set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us'
+
+;
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+set
+spanner.max_commit_delay
+to
+'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to%'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to_'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to&'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to$'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to@'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to!'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to*'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to('10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to)'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to-'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to+'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to-#'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to/'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to\'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to?'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to-/'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to/#'10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.max_commit_delay to '10000us';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to '10000us'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay to/-'10000us';
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+SET SPANNER.MAX_COMMIT_DELAY TO '9223372036854775807NS';
+NEW_CONNECTION;
+set spanner.max_commit_delay to '9223372036854775807ns';
+NEW_CONNECTION;
+ set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+ set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+
+
+
+set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns' ;
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns'
+
+;
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+set
+spanner.max_commit_delay
+TO
+'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO%'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO_'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO&'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO$'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO@'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO!'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO*'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO('9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO)'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO-'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO+'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO-#'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO/'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO\'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO?'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO-/'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO/#'9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.max_commit_delay TO '9223372036854775807ns';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO '9223372036854775807ns'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.max_commit_delay TO/-'9223372036854775807ns';
+NEW_CONNECTION;
set spanner.statement_tag='tag1';
NEW_CONNECTION;
SET SPANNER.STATEMENT_TAG='TAG1';
@@ -67322,6 +68713,206 @@ NEW_CONNECTION;
@EXPECT EXCEPTION INVALID_ARGUMENT
set spanner.statement_tag to/-'';
NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+SET SPANNER.STATEMENT_TAG TO 'TEST_TAG';
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+ set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+ set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+
+
+
+set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag' ;
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag' ;
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag'
+
+;
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+set
+spanner.statement_tag
+to
+'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to%'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to_'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to&'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to$'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to@'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to!'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to*'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to('test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to)'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to-'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to+'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to-#'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to/'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to\'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to?'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to-/'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to/#'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.statement_tag to 'test_tag';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to 'test_tag'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.statement_tag to/-'test_tag';
+NEW_CONNECTION;
set autocommit = false;
set spanner.transaction_tag='tag1';
NEW_CONNECTION;
@@ -68924,6 +70515,274 @@ set autocommit = false;
@EXPECT EXCEPTION INVALID_ARGUMENT
set spanner.transaction_tag to/-'';
NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+SET SPANNER.TRANSACTION_TAG TO 'TEST_TAG';
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+ set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+ set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+
+
+
+set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag' ;
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag' ;
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag'
+
+;
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+set
+spanner.transaction_tag
+to
+'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag' bar;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'%;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to%'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'_;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to_'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'&;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to&'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'$;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to$'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'@;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to@'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'!;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to!'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'*;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to*'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'(;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to('test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag');
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to)'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'-;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to-'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'+;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to+'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'-#;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to-#'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'/;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to/'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'\;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to\'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'?;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to?'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'-/;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to-/'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'/#;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to/#'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set spanner.transaction_tag to 'test_tag';
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to 'test_tag'/-;
+NEW_CONNECTION;
+set autocommit = false;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set spanner.transaction_tag to/-'test_tag';
+NEW_CONNECTION;
set spanner.rpc_priority='HIGH';
NEW_CONNECTION;
SET SPANNER.RPC_PRIORITY='HIGH';
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ConnectionImplGeneratedSqlScriptTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ConnectionImplGeneratedSqlScriptTest.sql
index c64d2b6ce13..e35ae5f3c3c 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ConnectionImplGeneratedSqlScriptTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ConnectionImplGeneratedSqlScriptTest.sql
@@ -160,15 +160,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:16.008000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:16.008000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:32.957000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:32.957000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:16.008000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:32.957000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -510,15 +510,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SET SPANNER.READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:16.391000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:16.391000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.060000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.060000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SET SPANNER.READ_ONLY_STALENESS='EXACT_STALENESS 10s';
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:16.391000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.060000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -950,8 +950,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:16.882000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:16.882000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.165000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.165000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -961,7 +961,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
ROLLBACK;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:16.882000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.165000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -1462,8 +1462,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:17.326000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:17.326000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.275000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.275000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -1473,7 +1473,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:17.326000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.275000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -1876,15 +1876,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:17.672000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:17.672000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.351000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.351000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:17.672000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.351000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2243,14 +2243,14 @@ SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:18.117000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.428000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:18.117000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.428000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2600,13 +2600,13 @@ SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:18.509000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.509000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:18.509000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.509000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -2910,14 +2910,14 @@ SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:18.834000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:18.834000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.575000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.575000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:18.834000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.575000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=FALSE;
@@ -3114,7 +3114,6 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -3246,15 +3245,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:19.286000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:19.286000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.662000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.662000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:19.286000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.662000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -3486,7 +3485,6 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -3664,8 +3662,8 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:19.688000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:19.688000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.747000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.747000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -3674,7 +3672,7 @@ START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
RUN BATCH;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:19.688000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.747000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4083,14 +4081,14 @@ SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:20.139000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.806000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:20.139000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.806000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4440,13 +4438,13 @@ SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:20.445000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.861000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:20.445000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.861000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -4679,7 +4677,6 @@ SET TRANSACTION READ ONLY;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -4880,8 +4877,8 @@ SET TRANSACTION READ ONLY;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:20.850000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:20.850000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.927000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.927000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -4891,7 +4888,7 @@ SET TRANSACTION READ ONLY;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:20.850000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.927000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5291,15 +5288,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET TRANSACTION READ ONLY;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.197000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.197000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:33.985000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:33.985000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET TRANSACTION READ ONLY;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.197000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:33.985000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5513,7 +5510,6 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET SPANNER.READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -5645,15 +5641,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET SPANNER.READ_ONLY_STALENESS='EXACT_STALENESS 10s';
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.478000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.478000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.041000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.041000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SET SPANNER.READ_ONLY_STALENESS='EXACT_STALENESS 10s';
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.478000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.041000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -5892,7 +5888,6 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -6093,8 +6088,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
ROLLBACK;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:21.844000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:21.844000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.108000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.108000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -6104,7 +6099,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
ROLLBACK;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:21.844000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.108000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -6412,7 +6407,6 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -6613,8 +6607,8 @@ BEGIN TRANSACTION;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:22.407000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:22.407000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.191000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.191000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -6624,7 +6618,7 @@ BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:22.407000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.191000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7029,15 +7023,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:22.697000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:22.697000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.254000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.254000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:22.697000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.254000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7400,14 +7394,14 @@ SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.054000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.318000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.054000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.318000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7762,13 +7756,13 @@ SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.432000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.384000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
SELECT 1 AS TEST;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.432000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.384000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -7973,7 +7967,6 @@ UPDATE foo SET bar=1;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
-@EXPECT EXCEPTION FAILED_PRECONDITION
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -8082,14 +8075,14 @@ SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:23.761000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:23.761000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.443000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.443000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:23.761000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.443000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=FALSE;
@@ -8399,13 +8392,13 @@ SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.025000000Z';
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.492000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
START BATCH DDL;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.025000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.492000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -8760,8 +8753,8 @@ SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SET TRANSACTION READ ONLY;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.320000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.320000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.542000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.542000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -8769,7 +8762,7 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SET TRANSACTION READ ONLY;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.320000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.542000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -9204,8 +9197,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
UPDATE foo SET bar=1;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.676000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.676000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.611000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.611000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -9213,8 +9206,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
UPDATE foo SET bar=1;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.676000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:24.676000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.611000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.611000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -9600,15 +9593,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:24.973000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:24.973000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.665000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.665000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:24.973000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.665000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@@ -9959,15 +9952,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.308000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.308000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.719000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.719000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id);
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.308000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.308000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.719000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.719000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -10327,15 +10320,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
UPDATE foo SET bar=1;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.588000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.588000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.800000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.800000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
UPDATE foo SET bar=1;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.588000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.588000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.800000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.800000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -10725,16 +10718,16 @@ SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:25.921000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:25.921000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.857000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.857000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
@EXPECT RESULT_SET 'TEST',1
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:25.921000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:25.921000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.857000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.857000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -11117,15 +11110,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.199000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.199000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.918000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.918000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.199000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.199000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.918000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.918000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -11455,14 +11448,14 @@ SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.495000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.495000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:34.974000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:34.974000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
SET AUTOCOMMIT=TRUE;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.495000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.495000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:34.974000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:34.974000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=FALSE;
@@ -11785,15 +11778,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SET SPANNER.READ_ONLY_STALENESS='MAX_STALENESS 10s';
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:26.744000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:26.744000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.024000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.024000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SET SPANNER.READ_ONLY_STALENESS='MAX_STALENESS 10s';
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:26.744000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:26.744000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.024000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.024000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -12200,8 +12193,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.080000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.080000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.080000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.080000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -12209,8 +12202,8 @@ SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
SELECT 1 AS TEST;
COMMIT;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.080000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:27.080000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.080000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.080000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -12593,15 +12586,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.369000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.369000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.129000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.129000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
BEGIN TRANSACTION;
@EXPECT EXCEPTION FAILED_PRECONDITION
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.369000000Z';
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.129000000Z';
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
@@ -12939,15 +12932,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.669000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.669000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.184000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.184000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.669000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:27.669000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.184000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.184000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -13294,15 +13287,15 @@ NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:27.959000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:27.959000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.242000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.242000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
SELECT 1 AS TEST;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:27.959000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:27.959000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.242000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.242000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
@@ -13619,14 +13612,14 @@ SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
-SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-03-26T15:31:28.213000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-03-26T15:31:28.213000000Z'
+SET SPANNER.READ_ONLY_STALENESS='READ_TIMESTAMP 2024-04-22T15:43:35.293000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','READ_TIMESTAMP 2024-04-22T15:43:35.293000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
SET AUTOCOMMIT=TRUE;
-SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-03-26T15:31:28.213000000Z';
-@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-03-26T15:31:28.213000000Z'
+SET SPANNER.READ_ONLY_STALENESS='MIN_READ_TIMESTAMP 2024-04-22T15:43:35.293000000Z';
+@EXPECT RESULT_SET 'SPANNER.READ_ONLY_STALENESS','MIN_READ_TIMESTAMP 2024-04-22T15:43:35.293000000Z'
SHOW VARIABLE SPANNER.READ_ONLY_STALENESS;
NEW_CONNECTION;
SET SPANNER.READONLY=TRUE;
diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
index 5e57f9fbb96..c2dabff690d 100644
--- a/grpc-google-cloud-spanner-admin-database-v1/pom.xml
+++ b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
grpc-google-cloud-spanner-admin-database-v1
GRPC library for grpc-google-cloud-spanner-admin-database-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
index d1511ecde81..1d37e0a7ecc 100644
--- a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
+++ b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
grpc-google-cloud-spanner-admin-instance-v1
GRPC library for grpc-google-cloud-spanner-admin-instance-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/grpc-google-cloud-spanner-executor-v1/pom.xml b/grpc-google-cloud-spanner-executor-v1/pom.xml
index 19bed8f9e7e..02b9b9a4cf3 100644
--- a/grpc-google-cloud-spanner-executor-v1/pom.xml
+++ b/grpc-google-cloud-spanner-executor-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
grpc-google-cloud-spanner-executor-v1
- 6.65.1
+ 6.66.0
grpc-google-cloud-spanner-executor-v1
GRPC library for google-cloud-spanner
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml
index a1ffaec8604..416ec81e640 100644
--- a/grpc-google-cloud-spanner-v1/pom.xml
+++ b/grpc-google-cloud-spanner-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
grpc-google-cloud-spanner-v1
GRPC library for grpc-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/pom.xml b/pom.xml
index 09d67fc0da3..380dd7a5a33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloud
google-cloud-spanner-parent
pom
- 6.65.1
+ 6.66.0
Google Cloud Spanner Parent
https://github.com/googleapis/java-spanner
@@ -14,7 +14,7 @@
com.google.cloud
sdk-platform-java-config
- 3.29.0
+ 3.30.0
@@ -61,47 +61,47 @@
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-executor-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
com.google.cloud
google-cloud-spanner
- 6.65.1
+ 6.66.0
diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml
index bc81f7897c9..703d3a48917 100644
--- a/proto-google-cloud-spanner-admin-database-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.65.1
+ 6.66.0
proto-google-cloud-spanner-admin-database-v1
PROTO library for proto-google-cloud-spanner-admin-database-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
index 5f99c4cbd10..234537b46d0 100644
--- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.65.1
+ 6.66.0
proto-google-cloud-spanner-admin-instance-v1
PROTO library for proto-google-cloud-spanner-admin-instance-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/proto-google-cloud-spanner-executor-v1/pom.xml b/proto-google-cloud-spanner-executor-v1/pom.xml
index dcddc4fd462..ad9fe73b971 100644
--- a/proto-google-cloud-spanner-executor-v1/pom.xml
+++ b/proto-google-cloud-spanner-executor-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.65.1
+ 6.66.0
proto-google-cloud-spanner-executor-v1
Proto library for google-cloud-spanner
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml
index 8403789f397..14c9f9b5ad3 100644
--- a/proto-google-cloud-spanner-v1/pom.xml
+++ b/proto-google-cloud-spanner-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.65.1
+ 6.66.0
proto-google-cloud-spanner-v1
PROTO library for proto-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.65.1
+ 6.66.0
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 0765c5285f7..bc846dfdc77 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -33,7 +33,7 @@
com.google.cloud
google-cloud-spanner
- 6.65.0
+ 6.65.1
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 71d2979a0b7..6ffc3d57db9 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -32,7 +32,7 @@
com.google.cloud
google-cloud-spanner
- 6.65.1
+ 6.66.0
diff --git a/versions.txt b/versions.txt
index f7fe285588e..0f6c4274453 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,13 +1,13 @@
# Format:
# module:released-version:current-version
-proto-google-cloud-spanner-admin-instance-v1:6.65.1:6.65.1
-proto-google-cloud-spanner-v1:6.65.1:6.65.1
-proto-google-cloud-spanner-admin-database-v1:6.65.1:6.65.1
-grpc-google-cloud-spanner-v1:6.65.1:6.65.1
-grpc-google-cloud-spanner-admin-instance-v1:6.65.1:6.65.1
-grpc-google-cloud-spanner-admin-database-v1:6.65.1:6.65.1
-google-cloud-spanner:6.65.1:6.65.1
-google-cloud-spanner-executor:6.65.1:6.65.1
-proto-google-cloud-spanner-executor-v1:6.65.1:6.65.1
-grpc-google-cloud-spanner-executor-v1:6.65.1:6.65.1
+proto-google-cloud-spanner-admin-instance-v1:6.66.0:6.66.0
+proto-google-cloud-spanner-v1:6.66.0:6.66.0
+proto-google-cloud-spanner-admin-database-v1:6.66.0:6.66.0
+grpc-google-cloud-spanner-v1:6.66.0:6.66.0
+grpc-google-cloud-spanner-admin-instance-v1:6.66.0:6.66.0
+grpc-google-cloud-spanner-admin-database-v1:6.66.0:6.66.0
+google-cloud-spanner:6.66.0:6.66.0
+google-cloud-spanner-executor:6.66.0:6.66.0
+proto-google-cloud-spanner-executor-v1:6.66.0:6.66.0
+grpc-google-cloud-spanner-executor-v1:6.66.0:6.66.0