diff --git a/.kokoro/nightly/integration.cfg b/.kokoro/nightly/integration.cfg
index 9640e74d453..5a95c68284c 100644
--- a/.kokoro/nightly/integration.cfg
+++ b/.kokoro/nightly/integration.cfg
@@ -13,12 +13,12 @@ env_vars: {
# TODO: remove this after we've migrated all tests and scripts
env_vars: {
key: "GCLOUD_PROJECT"
- value: "cloud-java-ci-sample"
+ value: "java-docs-samples-testing"
}
env_vars: {
key: "GOOGLE_CLOUD_PROJECT"
- value: "cloud-java-ci-sample"
+ value: "java-docs-samples-testing"
}
env_vars: {
diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index 5d3beb2e7b2..4b10a3bf2c9 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -631,4 +631,16 @@
com/google/cloud/spanner/connection/Connection
void setDirectedRead(com.google.spanner.v1.DirectedReadOptions)
+
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ java.time.Duration getMaxCommitDelay()
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ void setMaxCommitDelay(java.time.Duration)
+
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 ffcef91d9c9..d43e14a177b 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
@@ -41,6 +41,7 @@
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ResultSetStats;
+import java.time.Duration;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -556,6 +557,16 @@ default String getOptimizerStatisticsPackage() {
/** @return true if this connection requests commit statistics from Cloud Spanner */
boolean isReturnCommitStats();
+ /** Sets the max_commit_delay that will be applied to commit requests from this connection. */
+ default void setMaxCommitDelay(Duration maxCommitDelay) {
+ throw new UnsupportedOperationException("Unimplemented");
+ }
+
+ /** Returns the max_commit_delay that will be applied to commit requests from this connection. */
+ default Duration getMaxCommitDelay() {
+ throw new UnsupportedOperationException("Unimplemented");
+ }
+
/**
* Sets the priority to use for RPCs executed by this connection..
*
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 8ff367b2486..96c509a16aa 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
@@ -56,6 +56,7 @@
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.ResultSetStats;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -245,6 +246,8 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
private String transactionTag;
private String statementTag;
+ private Duration maxCommitDelay;
+
/** Create a connection and register it in the SpannerPool. */
ConnectionImpl(ConnectionOptions options) {
Preconditions.checkNotNull(options);
@@ -273,6 +276,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
this.autoPartitionMode = options.isAutoPartitionMode();
this.maxPartitions = options.getMaxPartitions();
this.maxPartitionedParallelism = options.getMaxPartitionedParallelism();
+ this.maxCommitDelay = options.getMaxCommitDelay();
this.ddlClient = createDdlClient();
setDefaultTransactionOptions();
}
@@ -791,6 +795,18 @@ public boolean isReturnCommitStats() {
return this.returnCommitStats;
}
+ @Override
+ public void setMaxCommitDelay(Duration maxCommitDelay) {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ this.maxCommitDelay = maxCommitDelay;
+ }
+
+ @Override
+ public Duration getMaxCommitDelay() {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ return this.maxCommitDelay;
+ }
+
@Override
public void setDelayTransactionStartUntilFirstWrite(
boolean delayTransactionStartUntilFirstWrite) {
@@ -1614,6 +1630,7 @@ UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
.setReadOnlyStaleness(readOnlyStaleness)
.setAutocommitDmlMode(autocommitDmlMode)
.setReturnCommitStats(returnCommitStats)
+ .setMaxCommitDelay(maxCommitDelay)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.build();
@@ -1636,6 +1653,7 @@ UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
.setRetryAbortsInternally(retryAbortsInternally)
.setSavepointSupport(savepointSupport)
.setReturnCommitStats(returnCommitStats)
+ .setMaxCommitDelay(maxCommitDelay)
.setTransactionRetryListeners(transactionRetryListeners)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
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 8bca0b2834c..de3c6ad33e2 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
@@ -44,6 +44,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -348,6 +349,9 @@ private static String generateGuardedConnectionPropertyError(
ConnectionProperty.createStringProperty(
OPTIMIZER_STATISTICS_PACKAGE_PROPERTY_NAME, ""),
ConnectionProperty.createBooleanProperty("returnCommitStats", "", false),
+ ConnectionProperty.createStringProperty(
+ "maxCommitDelay",
+ "The maximum commit delay in milliseconds that should be applied to commit requests from this connection."),
ConnectionProperty.createBooleanProperty(
"autoConfigEmulator",
"Automatically configure the connection to try to connect to the Cloud Spanner emulator (true/false). "
@@ -680,6 +684,7 @@ public static Builder newBuilder() {
private final String userAgent;
private final QueryOptions queryOptions;
private final boolean returnCommitStats;
+ private final Long maxCommitDelay;
private final boolean autoConfigEmulator;
private final Dialect dialect;
private final RpcPriority rpcPriority;
@@ -735,6 +740,7 @@ private ConnectionOptions(Builder builder) {
queryOptionsBuilder.setOptimizerStatisticsPackage(parseOptimizerStatisticsPackage(this.uri));
this.queryOptions = queryOptionsBuilder.build();
this.returnCommitStats = parseReturnCommitStats(this.uri);
+ this.maxCommitDelay = parseMaxCommitDelay(this.uri);
this.autoConfigEmulator = parseAutoConfigEmulator(this.uri);
this.dialect = parseDialect(this.uri);
this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri);
@@ -1051,6 +1057,27 @@ static boolean parseReturnCommitStats(String uri) {
return Boolean.parseBoolean(value);
}
+ @VisibleForTesting
+ static Long parseMaxCommitDelay(String uri) {
+ String value = parseUriProperty(uri, "maxCommitDelay");
+ try {
+ Long millis = value == null ? null : Long.valueOf(value);
+ if (millis != null && millis < 0L) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT, "maxCommitDelay must be >=0");
+ }
+ return millis;
+ } catch (NumberFormatException numberFormatException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Invalid value for maxCommitDelay: "
+ + value
+ + "\n"
+ + "The value must be a positive integer indicating the number of "
+ + "milliseconds to use as the max delay.");
+ }
+ }
+
static boolean parseAutoConfigEmulator(String uri) {
String value = parseUriProperty(uri, "autoConfigEmulator");
return Boolean.parseBoolean(value);
@@ -1382,6 +1409,11 @@ public boolean isReturnCommitStats() {
return returnCommitStats;
}
+ /** The max_commit_delay that should be applied to commit operations on this connection. */
+ public Duration getMaxCommitDelay() {
+ return maxCommitDelay == null ? null : Duration.ofMillis(maxCommitDelay);
+ }
+
/**
* Whether connections created by this {@link ConnectionOptions} will automatically try to connect
* to the emulator using the default host/port of the emulator, and automatically create the
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
index 3fbc3e7a8d4..cc4c53275b3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
@@ -82,6 +82,10 @@ interface ConnectionStatementExecutor {
StatementResult statementShowReturnCommitStats();
+ StatementResult statementSetMaxCommitDelay(Duration maxCommitDelay);
+
+ StatementResult statementShowMaxCommitDelay();
+
StatementResult statementSetDelayTransactionStartUntilFirstWrite(
Boolean delayTransactionStartUntilFirstWrite);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
index 0e8bdada223..ec0ca4f4ac3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
@@ -29,6 +29,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DEFAULT_TRANSACTION_ISOLATION;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DIRECTED_READ;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_STATISTICS_PACKAGE;
@@ -51,6 +52,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DATA_BOOST_ENABLED;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DIRECTED_READ;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_STATISTICS_PACKAGE;
@@ -343,6 +345,26 @@ public StatementResult statementShowReturnCommitStats() {
SHOW_RETURN_COMMIT_STATS);
}
+ @Override
+ public StatementResult statementSetMaxCommitDelay(Duration duration) {
+ getConnection()
+ .setMaxCommitDelay(
+ duration == null || duration.equals(Duration.getDefaultInstance())
+ ? null
+ : java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos()));
+ return noResult(SET_MAX_COMMIT_DELAY);
+ }
+
+ @Override
+ public StatementResult statementShowMaxCommitDelay() {
+ return resultSet(
+ "MAX_COMMIT_DELAY",
+ getConnection().getMaxCommitDelay() == null
+ ? null
+ : getConnection().getMaxCommitDelay().toMillis() + "ms",
+ SHOW_MAX_COMMIT_DELAY);
+ }
+
@Override
public StatementResult statementSetDelayTransactionStartUntilFirstWrite(
Boolean delayTransactionStartUntilFirstWrite) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
index 6c4290c3b18..05a8e899988 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
@@ -57,6 +57,7 @@
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.spanner.v1.SpannerGrpc;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -111,6 +112,7 @@ static class Builder extends AbstractMultiUseTransaction.Builder transactionRetryListeners;
@@ -137,6 +139,11 @@ Builder setReturnCommitStats(boolean returnCommitStats) {
return this;
}
+ Builder setMaxCommitDelay(Duration maxCommitDelay) {
+ this.maxCommitDelay = maxCommitDelay;
+ return this;
+ }
+
Builder setSavepointSupport(SavepointSupport savepointSupport) {
this.savepointSupport = savepointSupport;
return this;
@@ -180,6 +187,9 @@ private TransactionOption[] extractOptions(Builder builder) {
if (builder.returnCommitStats) {
numOptions++;
}
+ if (builder.maxCommitDelay != null) {
+ numOptions++;
+ }
if (this.transactionTag != null) {
numOptions++;
}
@@ -191,6 +201,9 @@ private TransactionOption[] extractOptions(Builder builder) {
if (builder.returnCommitStats) {
options[index++] = Options.commitStats();
}
+ if (builder.maxCommitDelay != null) {
+ options[index++] = Options.maxCommitDelay(builder.maxCommitDelay);
+ }
if (this.transactionTag != null) {
options[index++] = Options.tag(this.transactionTag);
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
index 56ddc16322a..164c3ae7ada 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
@@ -46,12 +46,14 @@
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
+import com.google.cloud.spanner.connection.ReadWriteTransaction.Builder;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.spanner.admin.database.v1.DatabaseAdminGrpc;
import com.google.spanner.v1.SpannerGrpc;
+import java.time.Duration;
import java.util.concurrent.Callable;
/**
@@ -78,6 +80,7 @@ class SingleUseTransaction extends AbstractBaseUnitOfWork {
private final TimestampBound readOnlyStaleness;
private final AutocommitDmlMode autocommitDmlMode;
private final boolean returnCommitStats;
+ private final Duration maxCommitDelay;
private final boolean internalMetdataQuery;
private volatile SettableApiFuture readTimestamp = null;
private volatile TransactionRunner writeTransaction;
@@ -92,6 +95,7 @@ static class Builder extends AbstractBaseUnitOfWork.Builder'|NULL",
+ "executorName": "ClientSideStatementSetExecutor",
+ "resultType": "NO_RESULT",
+ "statementType": "SET_MAX_COMMIT_DELAY",
+ "regex": "(?is)\\A\\s*set\\s+spanner\\.max_commit_delay(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
+ "method": "statementSetMaxCommitDelay",
+ "exampleStatements": ["set spanner.max_commit_delay=null", "set spanner.max_commit_delay='1s'", "set spanner.max_commit_delay='100ms'", "set spanner.max_commit_delay to '10000us'", "set spanner.max_commit_delay TO '9223372036854775807ns'"],
+ "setStatement": {
+ "propertyName": "SPANNER.MAX_COMMIT_DELAY",
+ "separator": "(?:=|\\s+TO\\s+)",
+ "allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|NULL)",
+ "converterName": "ClientSideStatementValueConverters$DurationConverter"
+ }
+ },
{
"name": "SET SPANNER.STATEMENT_TAG =|TO ''",
"executorName": "ClientSideStatementSetExecutor",
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
index bdca80a214d..9d319470020 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -40,6 +41,7 @@
import com.google.common.io.Files;
import java.io.File;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
@@ -1087,4 +1089,23 @@ public void testUseVirtualGrpcTransportThreads() {
.build()
.isUseVirtualThreads());
}
+
+ @Test
+ public void testMaxCommitDelay() {
+ assertNull(
+ ConnectionOptions.newBuilder()
+ .setUri(
+ "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getMaxCommitDelay());
+ assertEquals(
+ Duration.ofMillis(10L),
+ ConnectionOptions.newBuilder()
+ .setUri(
+ "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?maxCommitDelay=10")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getMaxCommitDelay());
+ }
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MaxCommitDelayTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MaxCommitDelayTest.java
new file mode 100644
index 00000000000..1e22986cce2
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MaxCommitDelayTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.MockSpannerServiceImpl;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.Statement;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.Duration;
+import com.google.spanner.v1.CommitRequest;
+import java.time.temporal.ChronoUnit;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MaxCommitDelayTest extends AbstractMockServerTest {
+
+ @Parameters(name = "dialect = {0}")
+ public static Collection