From 996668f934d2bcb7c6c4b364e7478469d76890be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 21 Aug 2023 08:52:22 +0200 Subject: [PATCH 1/4] docs: add sample for transaction timeouts Our current samples show how to set timeout and retry settings for all RPC invocations for a given RPC method, and how to set a timeout for a single statement, but we did not have a sample for setting a timeout for an entire transaction. This is something that customers have been asking multiple times. This sample shows how a transaction timeout can be set by using a gRPC context with a timeout. --- samples/snippets/pom.xml | 1 + .../spanner/TransactionTimeoutExample.java | 98 +++++++++++++++++++ .../spanner/SpannerStandaloneExamplesIT.java | 33 +++++++ 3 files changed, 132 insertions(+) create mode 100644 samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 8f312776692..4731895c8e2 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -17,6 +17,7 @@ com.google.cloud.samples shared-configuration 1.2.0 + diff --git a/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java new file mode 100644 index 00000000000..39ce0eff9bd --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020 Google Inc. + * + * 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.example.spanner; + +// [START spanner_transaction_timeout] + +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Statement; +import io.grpc.Context; +import io.grpc.Context.CancellableContext; +import io.grpc.Deadline; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Sample showing how to set a timeout for an entire transaction for the Cloud Spanner Java client. + */ +class TransactionTimeoutExample { + + static void executeTransactionWithTimeout() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + + executeTransactionWithTimeout(projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS); + } + + // Execute a read/write transaction with a timeout for the entire transaction. + static void executeTransactionWithTimeout( + String projectId, + String instanceId, + String databaseId, + long timeoutValue, + TimeUnit timeoutUnit) { + SpannerOptions.Builder builder = SpannerOptions.newBuilder().setProjectId(projectId); + try (Spanner spanner = builder.build().getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + // Create a gRPC context with a deadline and with cancellation. + // gRPC context deadlines require the use of a scheduled executor. + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + try (CancellableContext context = + Context.current() + .withDeadline(Deadline.after(timeoutValue, timeoutUnit), executor) + .withCancellation()) { + context.run( + () -> { + client + .readWriteTransaction() + .run( + transaction -> { + try (ResultSet resultSet = + transaction.executeQuery( + Statement.of( + "SELECT SingerId, FirstName, LastName\n" + + "FROM Singers\n" + + "ORDER BY LastName, FirstName"))) { + while (resultSet.next()) { + System.out.printf( + "%d %s %s\n", + resultSet.getLong("SingerId"), + resultSet.getString("FirstName"), + resultSet.getString("LastName")); + } + } + String sql = + "INSERT INTO Singers (SingerId, FirstName, LastName)\n" + + "VALUES (20, 'George', 'Washington')"; + long rowCount = transaction.executeUpdate(Statement.of(sql)); + System.out.printf("%d record inserted.%n", rowCount); + return null; + }); + }); + } + } + } +} +// [END spanner_transaction_timeout] diff --git a/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java index 03291a91f61..7c059bec1f1 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java @@ -17,15 +17,20 @@ package com.example.spanner; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Instance; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Value; import com.google.common.collect.ImmutableList; @@ -36,6 +41,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -133,6 +139,33 @@ public void executeSqlWithTimeout_shouldWriteData() { assertThat(out).contains("1 record inserted."); } + @Test + public void testTransactionWithTimeout_shouldWriteData() { + String projectId = spanner.getOptions().getProjectId(); + String out = + runExample( + () -> + TransactionTimeoutExample.executeTransactionWithTimeout( + projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS)); + assertTrue(out, out.contains("1 record inserted")); + } + + @Test + public void testTransactionWithTimeout_shouldFailWithDeadlineExceeded() { + String projectId = spanner.getOptions().getProjectId(); + // Execute a transaction with a 5 millisecond timeout. The transaction executes both a read, a + // write, and a commit operation. Each of these would normally take at least 5 milliseconds. + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + runExample( + () -> + TransactionTimeoutExample.executeTransactionWithTimeout( + projectId, instanceId, databaseId, 5L, TimeUnit.MILLISECONDS))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getErrorCode()); + } + @Test public void addNumericColumn_shouldSuccessfullyAddColumn() throws InterruptedException, ExecutionException { From 069bb720d7c8cc75f630c70455a4a4ddfe3e6ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 21 Aug 2023 08:54:49 +0200 Subject: [PATCH 2/4] chore: update copyright year --- .../java/com/example/spanner/TransactionTimeoutExample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java index 39ce0eff9bd..1fb01a34823 100644 --- a/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * Copyright 2023 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From cd8685ffd87298b22c5e912488338c6c50be27d3 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 21 Aug 2023 06:57:25 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b4e29261f8..5f5972cf193 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,13 @@ 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.45.2' +implementation 'com.google.cloud:google-cloud-spanner:6.45.3' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.2" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.3" ``` @@ -320,6 +320,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Statement Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) | | Tag Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TagSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TagSample.java) | | Tracing Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TracingSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TracingSample.java) | +| Transaction Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) | | Update Database Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) | | Update Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | | Update Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | @@ -430,7 +431,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.45.2 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.3 [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 From 668a63fee745dbee900fa7322ba24ae84c6ae321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 23 Aug 2023 08:57:24 +0200 Subject: [PATCH 4/4] chore: address review comments --- .../java/com/example/spanner/TransactionTimeoutExample.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java index 1fb01a34823..c9b92c74fbb 100644 --- a/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java @@ -52,8 +52,8 @@ static void executeTransactionWithTimeout( String databaseId, long timeoutValue, TimeUnit timeoutUnit) { - SpannerOptions.Builder builder = SpannerOptions.newBuilder().setProjectId(projectId); - try (Spanner spanner = builder.build().getService()) { + try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build() + .getService()) { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); // Create a gRPC context with a deadline and with cancellation.