From 134bf3340ceffa83dc06f77e7c0457de77972d55 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:54:13 +0000 Subject: [PATCH 01/10] chore(main): release 6.45.4-SNAPSHOT (#2596) :robot: I have created a release *beep* *boop* --- ### Updating meta-information for bleeding-edge SNAPSHOT release. --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- google-cloud-spanner-bom/pom.xml | 20 +++++++++---------- google-cloud-spanner-executor/pom.xml | 4 ++-- google-cloud-spanner/pom.xml | 4 ++-- .../pom.xml | 4 ++-- .../pom.xml | 4 ++-- grpc-google-cloud-spanner-v1/pom.xml | 4 ++-- pom.xml | 16 +++++++-------- .../pom.xml | 4 ++-- .../pom.xml | 4 ++-- proto-google-cloud-spanner-v1/pom.xml | 4 ++-- samples/snapshot/pom.xml | 2 +- versions.txt | 16 +++++++-------- 12 files changed, 43 insertions(+), 43 deletions(-) diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index a15b6b248eb..046ac7ce06d 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.45.3 + 6.45.4-SNAPSHOT pom com.google.cloud @@ -53,48 +53,48 @@ com.google.cloud google-cloud-spanner - 6.45.3 + 6.45.4-SNAPSHOT com.google.cloud google-cloud-spanner-executor - 6.45.3 + 6.45.4-SNAPSHOT com.google.cloud google-cloud-spanner test-jar - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index d38f27154e4..961c6aff39a 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.45.3 + 6.45.4-SNAPSHOT jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 464d087ef7a..3a7b71545a4 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.45.3 + 6.45.4-SNAPSHOT jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.45.3 + 6.45.4-SNAPSHOT google-cloud-spanner diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index d9c30274fb1..7475a8c7906 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.45.3 + 6.45.4-SNAPSHOT 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.45.3 + 6.45.4-SNAPSHOT diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index 6742ac954c4..e0fd32cded1 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.45.3 + 6.45.4-SNAPSHOT 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.45.3 + 6.45.4-SNAPSHOT diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 0dec3e97221..f6ac071fec9 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.45.3 + 6.45.4-SNAPSHOT grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/pom.xml b/pom.xml index a7f2c7f2c72..96d018c0dec 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.45.3 + 6.45.4-SNAPSHOT Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -62,37 +62,37 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.3 + 6.45.4-SNAPSHOT com.google.cloud google-cloud-spanner - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index 14d72213756..aa68fa8c1b1 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.45.3 + 6.45.4-SNAPSHOT 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.45.3 + 6.45.4-SNAPSHOT diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 6061e2162c2..35db7a3b902 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.45.3 + 6.45.4-SNAPSHOT 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.45.3 + 6.45.4-SNAPSHOT diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index 3e3c0f2a759..9f473df9328 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.45.3 + 6.45.4-SNAPSHOT proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 7df52fb3301..6d27e3bc059 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.45.3 + 6.45.4-SNAPSHOT diff --git a/versions.txt b/versions.txt index 4f05391afa5..ea31ae7356c 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.3 -proto-google-cloud-spanner-v1:6.45.3:6.45.3 -proto-google-cloud-spanner-admin-database-v1:6.45.3:6.45.3 -grpc-google-cloud-spanner-v1:6.45.3:6.45.3 -grpc-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.3 -grpc-google-cloud-spanner-admin-database-v1:6.45.3:6.45.3 -google-cloud-spanner:6.45.3:6.45.3 -google-cloud-spanner-executor:6.45.3:6.45.3 +proto-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.4-SNAPSHOT +proto-google-cloud-spanner-v1:6.45.3:6.45.4-SNAPSHOT +proto-google-cloud-spanner-admin-database-v1:6.45.3:6.45.4-SNAPSHOT +grpc-google-cloud-spanner-v1:6.45.3:6.45.4-SNAPSHOT +grpc-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.4-SNAPSHOT +grpc-google-cloud-spanner-admin-database-v1:6.45.3:6.45.4-SNAPSHOT +google-cloud-spanner:6.45.3:6.45.4-SNAPSHOT +google-cloud-spanner-executor:6.45.3:6.45.4-SNAPSHOT From dd3e9a0fe4846edcab9501b71c3d9e0fa24ed75b Mon Sep 17 00:00:00 2001 From: Gaurav Purohit Date: Fri, 18 Aug 2023 12:48:26 +0530 Subject: [PATCH 02/10] feat: Adding support for databoost (#2505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Adding support for databoost Enable databoost feature for all customers and removing the beta api annotation. * Enabling databoost test * Update ITBatchReadTest.java * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * docs fix * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: Rajat Bhatta <93644539+rajatbhatta@users.noreply.github.com> --- .../src/main/java/com/google/cloud/spanner/Options.java | 7 ++----- .../java/com/google/cloud/spanner/it/ITBatchReadTest.java | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java index 0d804bfd933..2bd35ec7853 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java @@ -16,7 +16,6 @@ package com.google.cloud.spanner; -import com.google.api.core.BetaApi; import com.google.common.base.Preconditions; import com.google.spanner.v1.RequestOptions.Priority; import java.io.Serializable; @@ -156,11 +155,9 @@ public static ListOption pageSize(int pageSize) { } /** - * If this is for a partitioned read and query and this field is set to `true`, the request will - * be executed via Spanner independent compute resources. The method is available in Beta mode - * (and is not generally available now). + * If this is for PartitionedRead or PartitionedQuery and this field is set to `true`, the request + * will be executed via Spanner independent compute resources. */ - @BetaApi public static DataBoostQueryOption dataBoostEnabled(Boolean dataBoostEnabled) { return new DataBoostQueryOption(dataBoostEnabled); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBatchReadTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBatchReadTest.java index 11be7df0430..6598a3dca76 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBatchReadTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBatchReadTest.java @@ -258,8 +258,6 @@ public void readUsingIndex() { @Test public void dataBoostRead() { - // TODO: Remove the following check during GA - assumeFalse("DataBoost feature is not yet generally available", true); assumeFalse("Emulator does not support data boost read", isUsingEmulator()); BitSet seenRows = new BitSet(numRows); @@ -314,8 +312,6 @@ private PartitionOptions getRandomPartitionOptions() { @Test public void dataBoostQuery() { - // TODO: Remove the following check during GA - assumeFalse("DataBoost feature is not yet generally available", true); assumeFalse("Emulator does not support data boost query", isUsingEmulator()); BitSet seenRows = new BitSet(numRows); TimestampBound bound = getRandomBound(); From 59cec9b9cdad169bd8de8ab7b264b04150dda7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 23 Aug 2023 09:36:13 +0200 Subject: [PATCH 03/10] docs: add sample for transaction timeouts (#2599) 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. --- README.md | 7 +- samples/snippets/pom.xml | 1 + .../spanner/TransactionTimeoutExample.java | 98 +++++++++++++++++++ .../spanner/SpannerStandaloneExamplesIT.java | 33 +++++++ 4 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java 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 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..c9b92c74fbb --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java @@ -0,0 +1,98 @@ +/* + * 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. + * 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) { + 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. + // 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 bfa777bc957a4abb2cbe3842a512e5cd652864eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 29 Aug 2023 15:45:00 +0200 Subject: [PATCH 04/10] chore: allow comments in PG strings (#2604) Allow comments in PostgreSQL query strings for extracting parameter definitions. The comments need to be preserved, as they could contain query hints. --- .../connection/AbstractStatementParser.java | 7 +- .../connection/PostgreSQLStatementParser.java | 57 ++- .../connection/StatementParserTest.java | 339 ++++++++++++------ 3 files changed, 286 insertions(+), 117 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java index 11549fbfc59..6c35fe5c96d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java @@ -539,11 +539,10 @@ public static class ParametersInfo { * Converts all positional parameters (?) in the given sql string into named parameters. The * parameters are named @p1, @p2, etc. This method is used when converting a JDBC statement that * uses positional parameters to a Cloud Spanner {@link Statement} instance that requires named - * parameters. The input SQL string may not contain any comments. There is an exception case if - * the statement starts with a GSQL comment which forces it to be interpreted as a GoogleSql - * statement. + * parameters. The input SQL string may not contain any comments, except for PostgreSQL-dialect + * SQL strings. * - * @param sql The sql string without comments that should be converted + * @param sql The sql string that should be converted * @return A {@link ParametersInfo} object containing a string with named parameters instead of * positional parameters and the number of parameters. * @throws SpannerException If the input sql string contains an unclosed string/byte literal. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java index 012bfbba875..8cb2b7e464a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java @@ -184,9 +184,8 @@ ParametersInfo convertPositionalParametersToNamedParametersInternal(char paramCh * Note: This is an internal API and breaking changes can be made without prior notice. * *

Returns the PostgreSQL-style query parameters ($1, $2, ...) in the given SQL string. The - * SQL-string is assumed to not contain any comments. Use {@link #removeCommentsAndTrim(String)} - * to remove all comments before calling this method. Occurrences of query-parameter like strings - * inside quoted identifiers or string literals are ignored. + * SQL-string is allowed to contain comments. Occurrences of query-parameter like strings inside + * quoted identifiers or string literals are ignored. * *

The following example will return a set containing ("$1", "$2"). * select col1, col2, "col$4" @@ -195,7 +194,7 @@ ParametersInfo convertPositionalParametersToNamedParametersInternal(char paramCh * and not col3=$1 and col4='$3' * * - * @param sql the SQL-string to check for parameters. Must not contain comments. + * @param sql the SQL-string to check for parameters. * @return A set containing all the parameters in the SQL-string. */ @InternalApi @@ -233,12 +232,56 @@ private int skip(String sql, int currentIndex, @Nullable StringBuilder result) { return skipQuoted( sql, currentIndex + dollarTag.length() + 1, currentChar, dollarTag, result); } + } else if (currentChar == HYPHEN + && sql.length() > (currentIndex + 1) + && sql.charAt(currentIndex + 1) == HYPHEN) { + return skipSingleLineComment(sql, currentIndex, result); + } else if (currentChar == SLASH + && sql.length() > (currentIndex + 1) + && sql.charAt(currentIndex + 1) == ASTERISK) { + return skipMultiLineComment(sql, currentIndex, result); } appendIfNotNull(result, currentChar); return currentIndex + 1; } + static int skipSingleLineComment(String sql, int currentIndex, @Nullable StringBuilder result) { + int endIndex = sql.indexOf('\n', currentIndex + 2); + if (endIndex == -1) { + endIndex = sql.length(); + } else { + // Include the newline character. + endIndex++; + } + appendIfNotNull(result, sql.substring(currentIndex, endIndex)); + return endIndex; + } + + static int skipMultiLineComment(String sql, int startIndex, @Nullable StringBuilder result) { + // Current position is start + '/*'.length(). + int pos = startIndex + 2; + // PostgreSQL allows comments to be nested. That is, the following is allowed: + // '/* test /* inner comment */ still a comment */' + int level = 1; + while (pos < sql.length()) { + if (sql.charAt(pos) == SLASH && sql.length() > (pos + 1) && sql.charAt(pos + 1) == ASTERISK) { + level++; + } + if (sql.charAt(pos) == ASTERISK && sql.length() > (pos + 1) && sql.charAt(pos + 1) == SLASH) { + level--; + if (level == 0) { + pos += 2; + appendIfNotNull(result, sql.substring(startIndex, pos)); + return pos; + } + } + pos++; + } + appendIfNotNull(result, sql.substring(startIndex)); + return sql.length(); + } + private int skipQuoted( String sql, int startIndex, char startQuote, @Nullable StringBuilder result) { return skipQuoted(sql, startIndex, startQuote, null, result); @@ -285,6 +328,12 @@ private void appendIfNotNull(@Nullable StringBuilder result, char currentChar) { } } + private static void appendIfNotNull(@Nullable StringBuilder result, String suffix) { + if (result != null) { + result.append(suffix); + } + } + private void appendIfNotNull( @Nullable StringBuilder result, char prefix, String tag, char suffix) { if (result != null) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java index 8246ca10760..1c20bc94ec6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java @@ -1143,121 +1143,198 @@ public void testGoogleStandardSQLDialectConvertPositionalParametersToNamedParame + "and col8 between @p12 and @p13"))); } + private enum CommentInjector { + NONE { + @Override + String inject(String sql, String comment) { + return String.format(sql, ""); + } + }, + BEFORE { + @Override + String inject(String sql, String comment) { + return comment + String.format(sql, ""); + } + }, + IN_THE_MIDDLE { + @Override + String inject(String sql, String comment) { + return String.format(sql, comment); + } + }, + AFTER { + @Override + String inject(String sql, String comment) { + return String.format(sql, "") + comment; + } + }; + + abstract String inject(String sql, String comment); + } + @Test public void testPostgreSQLDialectDialectConvertPositionalParametersToNamedParameters() { assumeTrue(dialect == Dialect.POSTGRESQL); - assertThat( + for (String comment : + new String[] { + "-- test comment\n", + "/* another test comment */", + "/* comment\nwith\nmultiple\nlines\n */", + "/* comment /* with nested */ comment */" + }) { + for (CommentInjector injector : CommentInjector.values()) { + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("select * %sfrom foo where name=?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("select * %sfrom foo where name=$1", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?%s'?test?\"?test?\"?'?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1%s'?test?\"?test?\"?'$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?'?it\\''?s'%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1'?it\\''?s'%s$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?'?it\\\"?s'%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1'?it\\\"?s'%s$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?\"?it\\\"\"?s\"%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1\"?it\\\"\"?s\"%s$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?%s'''?it\\''?s'''?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1%s'''?it\\''?s'''$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?\"\"\"?it\\\"\"?s\"\"\"%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1\"\"\"?it\\\"\"?s\"\"\"%s$2", comment)); + + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?$$?it$?s$$%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1$$?it$?s$$%s$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?$tag$?it$$?s$tag$%s?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1$tag$?it$$?s$tag$%s$2", comment)); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?%s$$?it\\'?s \n ?it\\'?s$$?", comment)) + .sqlWithNamedParameters) + .isEqualTo(injector.inject("$1%s$$?it\\'?s \n ?it\\'?s$$$2", comment)); + + // Note: PostgreSQL allows a single-quoted string literal to contain line feeds. + assertEquals( + injector.inject("$1'?it\\''?s \n ?it\\''?s'%s$2", comment), parser.convertPositionalParametersToNamedParameters( - '?', "select * from foo where name=?") - .sqlWithNamedParameters) - .isEqualTo("select * from foo where name=$1"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'?test?\"?test?\"?'?") - .sqlWithNamedParameters) - .isEqualTo("$1'?test?\"?test?\"?'$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'?it\\''?s'?") - .sqlWithNamedParameters) - .isEqualTo("$1'?it\\''?s'$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'?it\\\"?s'?") - .sqlWithNamedParameters) - .isEqualTo("$1'?it\\\"?s'$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?\"?it\\\"\"?s\"?") - .sqlWithNamedParameters) - .isEqualTo("$1\"?it\\\"\"?s\"$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\''?s'''?") - .sqlWithNamedParameters) - .isEqualTo("$1'''?it\\''?s'''$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?\"\"\"?it\\\"\"?s\"\"\"?") - .sqlWithNamedParameters) - .isEqualTo("$1\"\"\"?it\\\"\"?s\"\"\"$2"); - - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?$$?it$?s$$?") - .sqlWithNamedParameters) - .isEqualTo("$1$$?it$?s$$$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?$tag$?it$$?s$tag$?") - .sqlWithNamedParameters) - .isEqualTo("$1$tag$?it$$?s$tag$$2"); - assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?$$?it\\'?s \n ?it\\'?s$$?") - .sqlWithNamedParameters) - .isEqualTo("$1$$?it\\'?s \n ?it\\'?s$$$2"); - - // Note: PostgreSQL allows a single-quoted string literal to contain line feeds. - assertEquals( - "$1'?it\\''?s \n ?it\\''?s'$2", - parser.convertPositionalParametersToNamedParameters('?', "?'?it\\''?s \n ?it\\''?s'?") - .sqlWithNamedParameters); - assertUnclosedLiteral("?'?it\\''?s \n ?it\\''?s?"); - assertEquals( - "$1'''?it\\''?s \n ?it\\''?s'$2", - parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\''?s \n ?it\\''?s'?") - .sqlWithNamedParameters); - - assertThat( - parser.convertPositionalParametersToNamedParameters( - '?', - "select 1, ?, 'test?test', \"test?test\", foo.* from `foo` where col1=? and col2='test' and col3=? and col4='?' and col5=\"?\" and col6='?''?''?'") - .sqlWithNamedParameters, - is( - equalTo( - "select 1, $1, 'test?test', \"test?test\", foo.* from `foo` where col1=$2 and col2='test' and col3=$3 and col4='?' and col5=\"?\" and col6='?''?''?'"))); + '?', injector.inject("?'?it\\''?s \n ?it\\''?s'%s?", comment)) + .sqlWithNamedParameters); + assertUnclosedLiteral("?'?it\\''?s \n ?it\\''?s?"); + assertEquals( + injector.inject("$1%s'''?it\\''?s \n ?it\\''?s'$2", comment), + parser.convertPositionalParametersToNamedParameters( + '?', injector.inject("?%s'''?it\\''?s \n ?it\\''?s'?", comment)) + .sqlWithNamedParameters); - assertThat( - parser.convertPositionalParametersToNamedParameters( - '?', - "select * " + "from foo " + "where name=? " + "and col2 like ? " + "and col3 > ?") - .sqlWithNamedParameters, - is( - equalTo( - "select * " - + "from foo " - + "where name=$1 " - + "and col2 like $2 " - + "and col3 > $3"))); - assertThat( - parser.convertPositionalParametersToNamedParameters( - '?', "select * " + "from foo " + "where id between ? and ?") - .sqlWithNamedParameters, - is(equalTo("select * " + "from foo " + "where id between $1 and $2"))); - assertThat( - parser.convertPositionalParametersToNamedParameters( - '?', "select * " + "from foo " + "limit ? offset ?") - .sqlWithNamedParameters, - is(equalTo("select * " + "from foo " + "limit $1 offset $2"))); - assertThat( - parser.convertPositionalParametersToNamedParameters( - '?', - "select * " - + "from foo " - + "where col1=? " - + "and col2 like ? " - + "and col3 > ? " - + "and col4 < ? " - + "and col5 != ? " - + "and col6 not in (?, ?, ?) " - + "and col7 in (?, ?, ?) " - + "and col8 between ? and ?") - .sqlWithNamedParameters, - is( - equalTo( - "select * " - + "from foo " - + "where col1=$1 " - + "and col2 like $2 " - + "and col3 > $3 " - + "and col4 < $4 " - + "and col5 != $5 " - + "and col6 not in ($6, $7, $8) " - + "and col7 in ($9, $10, $11) " - + "and col8 between $12 and $13"))); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', + injector.inject( + "select 1, ?, 'test?test', \"test?test\", %sfoo.* from `foo` where col1=? and col2='test' and col3=? and col4='?' and col5=\"?\" and col6='?''?''?'", + comment)) + .sqlWithNamedParameters, + is( + equalTo( + injector.inject( + "select 1, $1, 'test?test', \"test?test\", %sfoo.* from `foo` where col1=$2 and col2='test' and col3=$3 and col4='?' and col5=\"?\" and col6='?''?''?'", + comment)))); + + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', + injector.inject( + "select * " + + "%sfrom foo " + + "where name=? " + + "and col2 like ? " + + "and col3 > ?", + comment)) + .sqlWithNamedParameters, + is( + equalTo( + injector.inject( + "select * " + + "%sfrom foo " + + "where name=$1 " + + "and col2 like $2 " + + "and col3 > $3", + comment)))); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', + injector.inject( + "select * " + "from foo " + "where id between ?%s and ?", comment)) + .sqlWithNamedParameters, + is( + equalTo( + injector.inject( + "select * " + "from foo " + "where id between $1%s and $2", comment)))); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', + injector.inject("select * " + "from foo " + "limit ? %s offset ?", comment)) + .sqlWithNamedParameters, + is( + equalTo( + injector.inject( + "select * " + "from foo " + "limit $1 %s offset $2", comment)))); + assertThat( + parser.convertPositionalParametersToNamedParameters( + '?', + injector.inject( + "select * " + + "from foo " + + "where col1=? " + + "and col2 like ? " + + " %s " + + "and col3 > ? " + + "and col4 < ? " + + "and col5 != ? " + + "and col6 not in (?, ?, ?) " + + "and col7 in (?, ?, ?) " + + "and col8 between ? and ?", + comment)) + .sqlWithNamedParameters, + is( + equalTo( + injector.inject( + "select * " + + "from foo " + + "where col1=$1 " + + "and col2 like $2 " + + " %s " + + "and col3 > $3 " + + "and col4 < $4 " + + "and col5 != $5 " + + "and col6 not in ($6, $7, $8) " + + "and col7 in ($9, $10, $11) " + + "and col8 between $12 and $13", + comment)))); + } + } } @Test @@ -1277,6 +1354,11 @@ public void testPostgreSQLGetQueryParameters() { assertEquals( ImmutableSet.of("$1"), parser.getQueryParameters("select '$2' from foo where bar=$1 and baz=$foo")); + assertEquals( + ImmutableSet.of("$1"), + parser.getQueryParameters( + "/* @lock_scanned_ranges = exclusive */ select -- random comment\n '$2' " + + "from foo /* comment /* with nested comment */ outside of nested comment */ where bar=$1 and baz=$foo")); } @Test @@ -1488,6 +1570,45 @@ public void testPostgreSQLReturningClause() { parser.parse(Statement.of("insert into t1 select 10.returning*")).hasReturningClause()); } + int skipSingleLineComment(String sql, int startIndex) { + return PostgreSQLStatementParser.skipSingleLineComment(sql, startIndex, null); + } + + int skipMultiLineComment(String sql, int startIndex) { + return PostgreSQLStatementParser.skipMultiLineComment(sql, startIndex, null); + } + + @Test + public void testSkipSingleLineComment() { + assumeTrue(dialect == Dialect.POSTGRESQL); + + assertEquals(7, skipSingleLineComment("-- foo\n", 0)); + assertEquals(7, skipSingleLineComment("-- foo\nbar", 0)); + assertEquals(6, skipSingleLineComment("-- foo", 0)); + assertEquals(11, skipSingleLineComment("bar -- foo\n", 4)); + assertEquals(11, skipSingleLineComment("bar -- foo\nbar", 4)); + assertEquals(10, skipSingleLineComment("bar -- foo", 4)); + } + + @Test + public void testSkipMultiLineComment() { + assumeTrue(dialect == Dialect.POSTGRESQL); + + assertEquals(9, skipMultiLineComment("/* foo */", 0)); + assertEquals(9, skipMultiLineComment("/* foo */ bar", 0)); + assertEquals(6, skipMultiLineComment("/* foo", 0)); + assertEquals(8, skipMultiLineComment("/* foo *", 0)); + assertEquals(9, skipMultiLineComment("/* foo **", 0)); + assertEquals(10, skipMultiLineComment("/* foo **/ ", 0)); + assertEquals(13, skipMultiLineComment("bar /* foo */", 4)); + assertEquals(13, skipMultiLineComment("bar /* foo */bar", 4)); + assertEquals(10, skipMultiLineComment("bar /* foo", 4)); + + assertEquals( + "/* foo /* inner comment */ not in inner comment */".length(), + skipMultiLineComment("/* foo /* inner comment */ not in inner comment */ bar", 0)); + } + private void assertUnclosedLiteral(String sql) { try { parser.convertPositionalParametersToNamedParameters('?', sql); From fbf1df9f3fb12faaead8634b88fd4843cbdedf5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 30 Aug 2023 17:04:30 +0200 Subject: [PATCH 05/10] feat: support PostgreSQL for autoConfigEmulator (#2601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support PostgreSQL for autoConfigEmulator The autoConfigEmulator=true flag in the Connection API can be used to automatically connect to the emulator and automatically create the instance and database that is being referenced. This makes running a quick test on the emulator much easier, as all you need to do is to configure the correct (JDBC) connection URL, and it will automatically work. This mode would always create a GoogleSQL database. This change adds support for creating a PostgreSQL database if the user specifically sets the dialect in the connection string. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../spanner/connection/ConnectionImpl.java | 3 +- .../spanner/connection/ConnectionOptions.java | 20 ++- .../spanner/connection/EmulatorUtil.java | 8 +- .../spanner/connection/EmulatorUtilTest.java | 122 ++++++++++++------ 4 files changed, 107 insertions(+), 46 deletions(-) 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 b5d4cdf8a2a..7cdfb9be52d 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 @@ -249,7 +249,8 @@ static UnitOfWorkType of(TransactionMode transactionMode) { this.options = options; this.spanner = spannerPool.getSpanner(options, this); if (options.isAutoConfigEmulator()) { - EmulatorUtil.maybeCreateInstanceAndDatabase(spanner, options.getDatabaseId()); + EmulatorUtil.maybeCreateInstanceAndDatabase( + spanner, options.getDatabaseId(), options.getDialect()); } this.dbClient = spanner.getDatabaseClient(options.getDatabaseId()); this.batchClient = spanner.getBatchClient(options.getDatabaseId()); 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 e7209eeab91..a5753eca8ca 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -26,6 +26,7 @@ import com.google.cloud.NoCredentials; import com.google.cloud.ServiceOptions; import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Options.RpcPriority; import com.google.cloud.spanner.SessionPoolOptions; @@ -307,7 +308,9 @@ public String[] getValidValues() { ConnectionProperty.createBooleanProperty("returnCommitStats", "", false), ConnectionProperty.createBooleanProperty( "autoConfigEmulator", - "Automatically configure the connection to try to connect to the Cloud Spanner emulator (true/false). The instance and database in the connection string will automatically be created if these do not yet exist on the emulator.", + "Automatically configure the connection to try to connect to the Cloud Spanner emulator (true/false). " + + "The instance and database in the connection string will automatically be created if these do not yet exist on the emulator. " + + "Add dialect=postgresql to the connection string to make sure that the database that is created uses the PostgreSQL dialect.", false), ConnectionProperty.createBooleanProperty( LENIENT_PROPERTY_NAME, @@ -317,7 +320,8 @@ public String[] getValidValues() { RPC_PRIORITY_NAME, "Sets the priority for all RPC invocations from this connection (HIGH/MEDIUM/LOW). The default is HIGH."), ConnectionProperty.createStringProperty( - DIALECT_PROPERTY_NAME, "Sets the dialect to use for this connection."), + DIALECT_PROPERTY_NAME, + "Sets the dialect to use for new databases that are created by this connection."), ConnectionProperty.createStringProperty( DATABASE_ROLE_PROPERTY_NAME, "Sets the database role to use for this connection. The default is privileges assigned to IAM role"), @@ -626,6 +630,7 @@ public static Builder newBuilder() { private final QueryOptions queryOptions; private final boolean returnCommitStats; private final boolean autoConfigEmulator; + private final Dialect dialect; private final RpcPriority rpcPriority; private final boolean delayTransactionStartUntilFirstWrite; private final boolean trackSessionLeaks; @@ -677,6 +682,7 @@ private ConnectionOptions(Builder builder) { this.queryOptions = queryOptionsBuilder.build(); this.returnCommitStats = parseReturnCommitStats(this.uri); this.autoConfigEmulator = parseAutoConfigEmulator(this.uri); + this.dialect = parseDialect(this.uri); this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri); this.host = determineHost(matcher, autoConfigEmulator, usePlainText); this.rpcPriority = parseRPCPriority(this.uri); @@ -939,6 +945,12 @@ static boolean parseAutoConfigEmulator(String uri) { return Boolean.parseBoolean(value); } + @VisibleForTesting + static Dialect parseDialect(String uri) { + String value = parseUriProperty(uri, DIALECT_PROPERTY_NAME); + return value != null ? Dialect.valueOf(value.toUpperCase()) : Dialect.GOOGLE_STANDARD_SQL; + } + @VisibleForTesting static boolean parseLenient(String uri) { String value = parseUriProperty(uri, LENIENT_PROPERTY_NAME); @@ -1259,6 +1271,10 @@ public boolean isAutoConfigEmulator() { return autoConfigEmulator; } + public Dialect getDialect() { + return dialect; + } + /** The {@link RpcPriority} to use for the connection. */ RpcPriority getRPCPriority() { return rpcPriority; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/EmulatorUtil.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/EmulatorUtil.java index ee291642031..d850c3d3839 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/EmulatorUtil.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/EmulatorUtil.java @@ -18,6 +18,7 @@ import com.google.cloud.NoCredentials; import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.InstanceConfigId; import com.google.cloud.spanner.InstanceInfo; @@ -41,8 +42,10 @@ class EmulatorUtil { * * @param spanner a {@link Spanner} instance that connects to an emulator instance * @param databaseId the id of the instance and the database to create + * @param dialect the {@link Dialect} to use for the database to create */ - static void maybeCreateInstanceAndDatabase(Spanner spanner, DatabaseId databaseId) { + static void maybeCreateInstanceAndDatabase( + Spanner spanner, DatabaseId databaseId, Dialect dialect) { Preconditions.checkArgument( NoCredentials.getInstance().equals(spanner.getOptions().getCredentials())); try { @@ -70,7 +73,8 @@ static void maybeCreateInstanceAndDatabase(Spanner spanner, DatabaseId databaseI .getDatabaseAdminClient() .createDatabase( databaseId.getInstanceId().getInstance(), - databaseId.getDatabase(), + dialect.createDatabaseStatementFor(databaseId.getDatabase()), + dialect, ImmutableList.of()) .get(); } catch (ExecutionException executionException) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/EmulatorUtilTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/EmulatorUtilTest.java index b9c556a9605..db1b232a37f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/EmulatorUtilTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/EmulatorUtilTest.java @@ -16,8 +16,9 @@ package com.google.cloud.spanner.connection; +import static com.google.cloud.spanner.connection.EmulatorUtil.maybeCreateInstanceAndDatabase; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -29,6 +30,7 @@ import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Instance; import com.google.cloud.spanner.InstanceAdminClient; @@ -45,10 +47,18 @@ import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class EmulatorUtilTest { + @Parameter public Dialect dialect; + + @Parameters(name = "dialect = {0}") + public static Object[] data() { + return Dialect.values(); + } @Test public void testCreateInstanceAndDatabase_bothSucceed() @@ -75,12 +85,15 @@ public void testCreateInstanceAndDatabase_bothSucceed() when(spanner.getDatabaseAdminClient()).thenReturn(databaseClient); when(databaseClient.createDatabase( - eq("test-instance"), eq("test-database"), eq(ImmutableList.of()))) + eq("test-instance"), + eq(dialect.createDatabaseStatementFor("test-database")), + eq(dialect), + eq(ImmutableList.of()))) .thenReturn(databaseOperationFuture); when(databaseOperationFuture.get()).thenReturn(mock(Database.class)); - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); + maybeCreateInstanceAndDatabase( + spanner, DatabaseId.of("test-project", "test-instance", "test-database"), dialect); // Verify that both the instance and the database was created. verify(instanceClient) @@ -90,7 +103,12 @@ public void testCreateInstanceAndDatabase_bothSucceed() .setInstanceConfigId(InstanceConfigId.of("test-project", "emulator-config")) .setNodeCount(1) .build()); - verify(databaseClient).createDatabase("test-instance", "test-database", ImmutableList.of()); + verify(databaseClient) + .createDatabase( + "test-instance", + dialect.createDatabaseStatementFor("test-database"), + dialect, + ImmutableList.of()); } @Test @@ -122,7 +140,10 @@ public void testCreateInstanceAndDatabase_bothFailWithAlreadyExists() when(spanner.getDatabaseAdminClient()).thenReturn(databaseClient); when(databaseClient.createDatabase( - eq("test-instance"), eq("test-database"), eq(ImmutableList.of()))) + eq("test-instance"), + eq(dialect.createDatabaseStatementFor("test-database")), + eq(dialect), + eq(ImmutableList.of()))) .thenReturn(databaseOperationFuture); when(databaseOperationFuture.get()) .thenThrow( @@ -130,8 +151,8 @@ public void testCreateInstanceAndDatabase_bothFailWithAlreadyExists() SpannerExceptionFactory.newSpannerException( ErrorCode.ALREADY_EXISTS, "Database already exists"))); - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); + maybeCreateInstanceAndDatabase( + spanner, DatabaseId.of("test-project", "test-instance", "test-database"), dialect); // Verify that both the instance and the database was created. verify(instanceClient) @@ -141,7 +162,12 @@ public void testCreateInstanceAndDatabase_bothFailWithAlreadyExists() .setInstanceConfigId(InstanceConfigId.of("test-project", "emulator-config")) .setNodeCount(1) .build()); - verify(databaseClient).createDatabase("test-instance", "test-database", ImmutableList.of()); + verify(databaseClient) + .createDatabase( + "test-instance", + dialect.createDatabaseStatementFor("test-database"), + dialect, + ImmutableList.of()); } @Test @@ -166,13 +192,15 @@ public void testCreateInstanceAndDatabase_propagatesOtherErrorsOnInstanceCreatio SpannerExceptionFactory.newSpannerException( ErrorCode.INVALID_ARGUMENT, "Invalid instance options"))); - try { - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); - } + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + maybeCreateInstanceAndDatabase( + spanner, + DatabaseId.of("test-project", "test-instance", "test-database"), + dialect)); + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); } @Test @@ -193,13 +221,15 @@ public void testCreateInstanceAndDatabase_propagatesInterruptsOnInstanceCreation .thenReturn(instanceOperationFuture); when(instanceOperationFuture.get()).thenThrow(new InterruptedException()); - try { - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); - } + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + maybeCreateInstanceAndDatabase( + spanner, + DatabaseId.of("test-project", "test-instance", "test-database"), + dialect)); + assertEquals(ErrorCode.CANCELLED, exception.getErrorCode()); } @Test @@ -227,7 +257,10 @@ public void testCreateInstanceAndDatabase_propagatesOtherErrorsOnDatabaseCreatio when(spanner.getDatabaseAdminClient()).thenReturn(databaseClient); when(databaseClient.createDatabase( - eq("test-instance"), eq("test-database"), eq(ImmutableList.of()))) + eq("test-instance"), + eq(dialect.createDatabaseStatementFor("test-database")), + eq(dialect), + eq(ImmutableList.of()))) .thenReturn(databaseOperationFuture); when(databaseOperationFuture.get()) .thenThrow( @@ -235,13 +268,15 @@ public void testCreateInstanceAndDatabase_propagatesOtherErrorsOnDatabaseCreatio SpannerExceptionFactory.newSpannerException( ErrorCode.INVALID_ARGUMENT, "Invalid database options"))); - try { - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); - } + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + maybeCreateInstanceAndDatabase( + spanner, + DatabaseId.of("test-project", "test-instance", "test-database"), + dialect)); + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); } @Test @@ -269,16 +304,21 @@ public void testCreateInstanceAndDatabase_propagatesInterruptsOnDatabaseCreation when(spanner.getDatabaseAdminClient()).thenReturn(databaseClient); when(databaseClient.createDatabase( - eq("test-instance"), eq("test-database"), eq(ImmutableList.of()))) + eq("test-instance"), + eq(dialect.createDatabaseStatementFor("test-database")), + eq(dialect), + eq(ImmutableList.of()))) .thenReturn(databaseOperationFuture); when(databaseOperationFuture.get()).thenThrow(new InterruptedException()); - try { - EmulatorUtil.maybeCreateInstanceAndDatabase( - spanner, DatabaseId.of("test-project", "test-instance", "test-database")); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); - } + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + maybeCreateInstanceAndDatabase( + spanner, + DatabaseId.of("test-project", "test-instance", "test-database"), + dialect)); + assertEquals(ErrorCode.CANCELLED, exception.getErrorCode()); } } From 465df7bad12fbea7dbcf6dbabb1b29d088c42665 Mon Sep 17 00:00:00 2001 From: Sri Harsha CH <57220027+harshachinta@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:39:29 +0530 Subject: [PATCH 06/10] fix: fix kokoro windows java8 ci (#2573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: fix kokoro windows java8 ci * chore: update version * chore: update file * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .kokoro/build.bat | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.kokoro/build.bat b/.kokoro/build.bat index c896610a27d..067cf4a4c48 100644 --- a/.kokoro/build.bat +++ b/.kokoro/build.bat @@ -15,8 +15,4 @@ :: downstream client libraries before they are released. :: See documentation in type-shell-output.bat -set JAVA8_HOME=%JAVA_HOME:"=% -choco install -y openjdk11 -set JAVA11_HOME=C:\Program Files\Eclipse Adoptium\jdk-11.0.18.10-hotspot\ - "C:\Program Files\Git\bin\bash.exe" %~dp0build.sh From 78470cbae673d0c168759057004f100120374e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 6 Sep 2023 09:40:31 +0200 Subject: [PATCH 07/10] test: add a test for blind writes (#2595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add a test for blind writes Adds a test for blind writes before executing a read or SQL statement in a read/write transaction. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../spanner/InlineBeginTransactionTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java index eb3f65176b7..c7f5e396069 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java @@ -69,6 +69,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -1359,6 +1360,55 @@ public void testInlinedBeginTxWithOnlyMutations() { assertThat(countTransactionsStarted()).isEqualTo(1); } + @Test + public void testInlinedBeginTxWithMutationsBeforeFailedSqlStatement() { + Statement insert = Statement.of("insert into foo (id) values (1)"); + Statement update = Statement.of("update foo set value='Two' where id=2"); + mockSpanner.putStatementResult(StatementResult.update(insert, 1L)); + mockSpanner.putStatementResult(StatementResult.update(update, 1L)); + // This error will be returned the first time the ExecuteSql method is called. The error is + // cleared after the first call, meaning that the second attempt will succeed. + mockSpanner.setExecuteSqlExecutionTime( + SimulatedExecutionTime.ofException(Status.ALREADY_EXISTS.asRuntimeException())); + + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + AtomicInteger attempts = new AtomicInteger(); + client + .readWriteTransaction() + .run( + transaction -> { + attempts.incrementAndGet(); + // Buffer a blind write before executing a SQL statement. + transaction.buffer( + Collections.singletonList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).build())); + try { + transaction.executeUpdate(insert); + } catch (SpannerException exception) { + assertEquals(ErrorCode.ALREADY_EXISTS, exception.getErrorCode()); + // The error should only occur during the initial attempt. + assertEquals(1, attempts.get()); + } + // We need to execute one more statement in the transaction in order to force a + // retry. + assertEquals(1L, transaction.executeUpdate(update)); + return null; + }); + // The transaction should be retried once. + assertEquals(2, attempts.get()); + assertEquals(1, mockSpanner.countRequestsOfType(BeginTransactionRequest.class)); + // We get 3 ExecuteSql requests: + // 1. The initial attempt that carries a BeginTransaction option. + // 2. The retry attempt that does not use a BeginTransaction option. + // 3. The second UPDATE statement in the transaction that is only executed during the retry. + assertEquals(3, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)); + assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class)); + CommitRequest commit = mockSpanner.getRequestsOfType(CommitRequest.class).get(0); + // The mutations should only be applied once. + assertEquals(1, commit.getMutationsCount()); + } + @SuppressWarnings("resource") @Test public void testTransactionManagerInlinedBeginTx() { From 1f850e93cfa6f61b85f42ebb7d02f7f5cd0b440b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 6 Sep 2023 09:43:09 +0200 Subject: [PATCH 08/10] chore: deduplicate code for batch updates (#2591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: deduplicate code for batch updates De-duplicate code that is equal for both the sync and async version of the executeBatchUpdate methods. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../spanner/connection/ConnectionImpl.java | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) 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 7cdfb9be52d..674086e6cd0 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 @@ -1324,32 +1324,15 @@ public ResultSet analyzeUpdateStatement( @Override public long[] executeBatchUpdate(Iterable updates) { - Preconditions.checkNotNull(updates); - ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); - // Check that there are only DML statements in the input. - List parsedStatements = new LinkedList<>(); - for (Statement update : updates) { - ParsedStatement parsedStatement = getStatementParser().parse(update); - switch (parsedStatement.getType()) { - case UPDATE: - parsedStatements.add(parsedStatement); - break; - case CLIENT_SIDE: - case QUERY: - case DDL: - case UNKNOWN: - default: - throw SpannerExceptionFactory.newSpannerException( - ErrorCode.INVALID_ARGUMENT, - "The batch update list contains a statement that is not an update statement: " - + parsedStatement.getSqlWithoutComments()); - } - } - return get(internalExecuteBatchUpdateAsync(CallType.SYNC, parsedStatements)); + return get(internalExecuteBatchUpdateAsync(CallType.SYNC, parseUpdateStatements(updates))); } @Override public ApiFuture executeBatchUpdateAsync(Iterable updates) { + return internalExecuteBatchUpdateAsync(CallType.ASYNC, parseUpdateStatements(updates)); + } + + private List parseUpdateStatements(Iterable updates) { Preconditions.checkNotNull(updates); ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); // Check that there are only DML statements in the input. @@ -1371,7 +1354,7 @@ public ApiFuture executeBatchUpdateAsync(Iterable updates) { + parsedStatement.getSqlWithoutComments()); } } - return internalExecuteBatchUpdateAsync(CallType.ASYNC, parsedStatements); + return parsedStatements; } private QueryOption[] mergeDataBoost(QueryOption... options) { From 2851ce8880b69c1beaa5da02c30efc74bf2bdfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 6 Sep 2023 10:36:26 +0200 Subject: [PATCH 09/10] chore: add execute method with allowed return types (#2592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add execute method with allowed return types Adds an execute method to the Connection API that allows the caller to specify the allowed result types. This can be used by driver implementations, such as JDBC, to use a single execute method in the Connection API, while still making sure that the method only executes statements that it should. The current executeUpdate method in the Connection API does not overlap completely with semantics of executeUpdate in JDBC, as JDBC allows any statement type that does not return a ResultSet to be executed with that method. The Connection API only allows statements that return an update count. Instead of modifying the executeUpdate method in the Connection API to match the semantics of JDBC (which would be a breaking change), this method can be used generically for all execute*** methods in the JDBC driver, which again can be used to clean up some code there. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix: bad merge for clirr --------- Co-authored-by: Owl Bot --- .../clirr-ignored-differences.xml | 12 ++ .../cloud/spanner/connection/Connection.java | 30 +++++ .../spanner/connection/ConnectionImpl.java | 64 ++++++++- .../connection/ConnectionImplTest.java | 114 ++++++++++++++++ .../connection/ExceptionMockServerTest.java | 125 ++++++++++++++++++ 5 files changed, 344 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index c0ddc050ae5..42039d2d2b9 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -416,6 +416,18 @@ com/google/cloud/spanner/connection/Connection void setMaxPartitions(int) + + + 7012 + com/google/cloud/spanner/connection/Connection + com.google.cloud.spanner.connection.StatementResult execute(com.google.cloud.spanner.Statement, java.util.Set) + + 7012 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 8a8bd57f1c6..e58610b7d03 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.ExecuteBatchDmlRequest; import com.google.spanner.v1.ResultSetStats; import java.util.Iterator; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -949,6 +950,35 @@ default boolean isDelayTransactionStartUntilFirstWrite() { */ StatementResult execute(Statement statement); + /** + * Executes the given statement if allowed in the current {@link TransactionMode} and connection + * state, and if the result that would be returned is in the set of allowed result types. The + * statement will not be sent to Cloud Spanner if the result type would not be allowed. This + * method can be used by drivers that must limit the type of statements that are allowed for a + * given method, e.g. for the {@link java.sql.Statement#executeQuery(String)} and {@link + * java.sql.Statement#executeUpdate(String)} methods. + * + *

The returned value depends on the type of statement: + * + *

+ * + * @param statement The statement to execute + * @param allowedResultTypes The result types that this method may return. The statement will not + * be sent to Cloud Spanner if the statement would return a result that is not one of the + * types in this set. + * @return the result of the statement + */ + default StatementResult execute(Statement statement, Set allowedResultTypes) { + throw new UnsupportedOperationException("Not implemented"); + } + /** * Executes the given statement if allowed in the current {@link TransactionMode} and connection * state asynchronously. The returned value depends on the type of statement: 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 674086e6cd0..566a93e76bc 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 @@ -47,6 +47,7 @@ import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement; import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType; import com.google.cloud.spanner.connection.StatementExecutor.StatementTimeout; +import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.cloud.spanner.connection.UnitOfWork.CallType; import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState; import com.google.common.annotations.VisibleForTesting; @@ -60,12 +61,15 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.Stack; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import javax.annotation.Nullable; import org.threeten.bp.Instant; /** Implementation for {@link Connection}, the generic Spanner connection API (not JDBC). */ @@ -940,9 +944,20 @@ public void rollbackToSavepoint(String name) { @Override public StatementResult execute(Statement statement) { - Preconditions.checkNotNull(statement); + return internalExecute(Preconditions.checkNotNull(statement), null); + } + + @Override + public StatementResult execute(Statement statement, Set allowedResultTypes) { + return internalExecute( + Preconditions.checkNotNull(statement), Preconditions.checkNotNull(allowedResultTypes)); + } + + private StatementResult internalExecute( + Statement statement, @Nullable Set allowedResultTypes) { ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); ParsedStatement parsedStatement = getStatementParser().parse(statement, this.queryOptions); + checkResultTypeAllowed(parsedStatement, allowedResultTypes); switch (parsedStatement.getType()) { case CLIENT_SIDE: return parsedStatement @@ -969,6 +984,53 @@ public StatementResult execute(Statement statement) { "Unknown statement: " + parsedStatement.getSqlWithoutComments()); } + @VisibleForTesting + static void checkResultTypeAllowed( + ParsedStatement parsedStatement, @Nullable Set allowedResultTypes) { + if (allowedResultTypes == null) { + return; + } + ResultType resultType = getResultType(parsedStatement); + if (!allowedResultTypes.contains(resultType)) { + throw SpannerExceptionFactory.newSpannerException( + ErrorCode.INVALID_ARGUMENT, + "This statement returns a result of type " + + resultType + + ". Only statements that return a result of one of the following types are allowed: " + + allowedResultTypes.stream() + .map(ResultType::toString) + .collect(Collectors.joining(", "))); + } + } + + private static ResultType getResultType(ParsedStatement parsedStatement) { + switch (parsedStatement.getType()) { + case CLIENT_SIDE: + if (parsedStatement.getClientSideStatement().isQuery()) { + return ResultType.RESULT_SET; + } else if (parsedStatement.getClientSideStatement().isUpdate()) { + return ResultType.UPDATE_COUNT; + } else { + return ResultType.NO_RESULT; + } + case QUERY: + return ResultType.RESULT_SET; + case UPDATE: + if (parsedStatement.hasReturningClause()) { + return ResultType.RESULT_SET; + } else { + return ResultType.UPDATE_COUNT; + } + case DDL: + return ResultType.NO_RESULT; + case UNKNOWN: + default: + throw SpannerExceptionFactory.newSpannerException( + ErrorCode.INVALID_ARGUMENT, + "Unknown statement: " + parsedStatement.getSqlWithoutComments()); + } + } + @Override public AsyncStatementResult executeAsync(Statement statement) { Preconditions.checkNotNull(statement); 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 09d2f92a6dc..7c5ba508e8f 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 @@ -20,6 +20,7 @@ import static com.google.cloud.spanner.connection.AbstractConnectionImplTest.SELECT; import static com.google.cloud.spanner.connection.AbstractConnectionImplTest.UPDATE; import static com.google.cloud.spanner.connection.AbstractConnectionImplTest.expectSpannerException; +import static com.google.cloud.spanner.connection.ConnectionImpl.checkResultTypeAllowed; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -28,6 +29,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; @@ -73,6 +75,7 @@ import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.cloud.spanner.connection.UnitOfWork.CallType; import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState; +import com.google.common.collect.ImmutableSet; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.ResultSetStats; @@ -1624,4 +1627,115 @@ UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) { assertNull(connection.getTransactionTag()); } } + + @Test + public void testCheckResultTypeAllowed() { + AbstractStatementParser parser = + AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL); + String query = "select * from foo"; + String dml = "update foo set bar=1 where true"; + String dmlReturning = "insert into foo (id, value) values (1, 'One') then return id"; + String ddl = "create table foo"; + String set = "set readonly=true"; + String show = "show variable readonly"; + String start = "start batch dml"; + + // null means all statements should be allowed. + ImmutableSet allowedResultTypes = null; + checkResultTypeAllowed(parser.parse(Statement.of(query)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dml)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dmlReturning)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(ddl)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(set)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(show)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(); + assertThrowResultNotAllowed(parser, query, allowedResultTypes); + assertThrowResultNotAllowed(parser, dml, allowedResultTypes); + assertThrowResultNotAllowed(parser, dmlReturning, allowedResultTypes); + assertThrowResultNotAllowed(parser, ddl, allowedResultTypes); + assertThrowResultNotAllowed(parser, set, allowedResultTypes); + assertThrowResultNotAllowed(parser, show, allowedResultTypes); + assertThrowResultNotAllowed(parser, start, allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.RESULT_SET); + checkResultTypeAllowed(parser.parse(Statement.of(query)), allowedResultTypes); + assertThrowResultNotAllowed(parser, dml, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dmlReturning)), allowedResultTypes); + assertThrowResultNotAllowed(parser, ddl, allowedResultTypes); + assertThrowResultNotAllowed(parser, set, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(show)), allowedResultTypes); + assertThrowResultNotAllowed(parser, start, allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.UPDATE_COUNT); + assertThrowResultNotAllowed(parser, query, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dml)), allowedResultTypes); + assertThrowResultNotAllowed(parser, dmlReturning, allowedResultTypes); + assertThrowResultNotAllowed(parser, ddl, allowedResultTypes); + assertThrowResultNotAllowed(parser, set, allowedResultTypes); + assertThrowResultNotAllowed(parser, show, allowedResultTypes); + assertThrowResultNotAllowed(parser, start, allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.NO_RESULT); + assertThrowResultNotAllowed(parser, query, allowedResultTypes); + assertThrowResultNotAllowed(parser, dml, allowedResultTypes); + assertThrowResultNotAllowed(parser, dmlReturning, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(ddl)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(set)), allowedResultTypes); + assertThrowResultNotAllowed(parser, show, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.RESULT_SET, ResultType.UPDATE_COUNT); + checkResultTypeAllowed(parser.parse(Statement.of(query)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dml)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dmlReturning)), allowedResultTypes); + assertThrowResultNotAllowed(parser, ddl, allowedResultTypes); + assertThrowResultNotAllowed(parser, set, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(show)), allowedResultTypes); + assertThrowResultNotAllowed(parser, start, allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.RESULT_SET, ResultType.NO_RESULT); + checkResultTypeAllowed(parser.parse(Statement.of(query)), allowedResultTypes); + assertThrowResultNotAllowed(parser, dml, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dmlReturning)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(ddl)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(set)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(show)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); + + allowedResultTypes = ImmutableSet.of(ResultType.UPDATE_COUNT, ResultType.NO_RESULT); + assertThrowResultNotAllowed(parser, query, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dml)), allowedResultTypes); + assertThrowResultNotAllowed(parser, dmlReturning, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(ddl)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(set)), allowedResultTypes); + assertThrowResultNotAllowed(parser, show, allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); + + allowedResultTypes = + ImmutableSet.of(ResultType.RESULT_SET, ResultType.UPDATE_COUNT, ResultType.NO_RESULT); + checkResultTypeAllowed(parser.parse(Statement.of(query)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dml)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(dmlReturning)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(ddl)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(set)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(show)), allowedResultTypes); + checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); + } + + private void assertThrowResultNotAllowed( + AbstractStatementParser parser, String sql, ImmutableSet allowedResultTypes) { + SpannerException exception = + assertThrows( + SpannerException.class, + () -> checkResultTypeAllowed(parser.parse(Statement.of(sql)), allowedResultTypes)); + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); + assertTrue( + exception.getMessage(), + exception + .getMessage() + .contains( + "Only statements that return a result of one of the following types are allowed")); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ExceptionMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ExceptionMockServerTest.java index 4b239e4cf8c..fcc05405b2d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ExceptionMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ExceptionMockServerTest.java @@ -22,11 +22,18 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; import com.google.cloud.spanner.Mutation; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.connection.StatementResult.ResultType; +import com.google.common.collect.ImmutableSet; +import com.google.longrunning.Operation; +import com.google.protobuf.Any; +import com.google.protobuf.Empty; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import io.grpc.Status; import io.grpc.StatusRuntimeException; import java.util.Arrays; @@ -152,4 +159,122 @@ public void testCommitException() { assertEquals(0, exception.getSuppressed().length); } } + + @Test + public void testAllowedResultType() { + mockSpanner.putStatementResult( + StatementResult.query(SELECT_COUNT_STATEMENT, SELECT_COUNT_RESULTSET_BEFORE_INSERT)); + mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, 1L)); + mockSpanner.putStatementResult( + StatementResult.updateReturning( + INSERT_RETURNING_STATEMENT, SELECT_COUNT_RESULTSET_AFTER_INSERT)); + mockDatabaseAdmin.addResponse( + Operation.newBuilder() + .setDone(true) + .setResponse(Any.pack(Empty.getDefaultInstance())) + .setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance())) + .build()); + Statement ddl = Statement.of("create table foo"); + + try (Connection connection = createConnection()) { + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(SELECT_COUNT_STATEMENT, ImmutableSet.of())) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> + connection.execute( + SELECT_COUNT_STATEMENT, ImmutableSet.of(ResultType.UPDATE_COUNT))) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> + connection.execute( + SELECT_COUNT_STATEMENT, ImmutableSet.of(ResultType.NO_RESULT))) + .getErrorCode()); + assertNotNull( + connection + .execute(SELECT_COUNT_STATEMENT, ImmutableSet.of(ResultType.RESULT_SET)) + .getResultSet()); + + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(INSERT_STATEMENT, ImmutableSet.of())) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> + connection.execute(INSERT_STATEMENT, ImmutableSet.of(ResultType.RESULT_SET))) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(INSERT_STATEMENT, ImmutableSet.of(ResultType.NO_RESULT))) + .getErrorCode()); + assertNotNull( + connection + .execute(INSERT_STATEMENT, ImmutableSet.of(ResultType.UPDATE_COUNT)) + .getUpdateCount()); + + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(INSERT_RETURNING_STATEMENT, ImmutableSet.of())) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> + connection.execute( + INSERT_RETURNING_STATEMENT, ImmutableSet.of(ResultType.UPDATE_COUNT))) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> + connection.execute( + INSERT_RETURNING_STATEMENT, ImmutableSet.of(ResultType.NO_RESULT))) + .getErrorCode()); + assertNotNull( + connection + .execute(INSERT_RETURNING_STATEMENT, ImmutableSet.of(ResultType.RESULT_SET)) + .getResultSet()); + + // Commit the current transaction and switch to autocommit to allow DDL. + connection.commit(); + connection.setAutocommit(true); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows(SpannerException.class, () -> connection.execute(ddl, ImmutableSet.of())) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(ddl, ImmutableSet.of(ResultType.RESULT_SET))) + .getErrorCode()); + assertEquals( + ErrorCode.INVALID_ARGUMENT, + assertThrows( + SpannerException.class, + () -> connection.execute(ddl, ImmutableSet.of(ResultType.UPDATE_COUNT))) + .getErrorCode()); + assertNotNull(connection.execute(ddl, ImmutableSet.of(ResultType.NO_RESULT))); + } + } } From 823582c73a7185711ce5aa256e36da88afe0df03 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:17:21 +0200 Subject: [PATCH 10/10] chore(main): release 6.46.0 (#2598) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 18 +++++++++++++++++ google-cloud-spanner-bom/pom.xml | 20 +++++++++---------- google-cloud-spanner-executor/pom.xml | 4 ++-- google-cloud-spanner/pom.xml | 4 ++-- .../pom.xml | 4 ++-- .../pom.xml | 4 ++-- grpc-google-cloud-spanner-v1/pom.xml | 4 ++-- pom.xml | 16 +++++++-------- .../pom.xml | 4 ++-- .../pom.xml | 4 ++-- proto-google-cloud-spanner-v1/pom.xml | 4 ++-- samples/snapshot/pom.xml | 2 +- versions.txt | 16 +++++++-------- 13 files changed, 61 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ddfdfc4972..ac8a686719a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [6.46.0](https://github.com/googleapis/java-spanner/compare/v6.45.3...v6.46.0) (2023-09-06) + + +### Features + +* Adding support for databoost ([#2505](https://github.com/googleapis/java-spanner/issues/2505)) ([dd3e9a0](https://github.com/googleapis/java-spanner/commit/dd3e9a0fe4846edcab9501b71c3d9e0fa24ed75b)) +* Support PostgreSQL for autoConfigEmulator ([#2601](https://github.com/googleapis/java-spanner/issues/2601)) ([fbf1df9](https://github.com/googleapis/java-spanner/commit/fbf1df9f3fb12faaead8634b88fd4843cbdedf5b)) + + +### Bug Fixes + +* Fix kokoro windows java8 ci ([#2573](https://github.com/googleapis/java-spanner/issues/2573)) ([465df7b](https://github.com/googleapis/java-spanner/commit/465df7bad12fbea7dbcf6dbabb1b29d088c42665)) + + +### Documentation + +* Add sample for transaction timeouts ([#2599](https://github.com/googleapis/java-spanner/issues/2599)) ([59cec9b](https://github.com/googleapis/java-spanner/commit/59cec9b9cdad169bd8de8ab7b264b04150dda7fb)) + ## [6.45.3](https://github.com/googleapis/java-spanner/compare/v6.45.2...v6.45.3) (2023-08-17) diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index 046ac7ce06d..f1389078ea2 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.45.4-SNAPSHOT + 6.46.0 pom com.google.cloud @@ -53,48 +53,48 @@ com.google.cloud google-cloud-spanner - 6.45.4-SNAPSHOT + 6.46.0 com.google.cloud google-cloud-spanner-executor - 6.45.4-SNAPSHOT + 6.46.0 com.google.cloud google-cloud-spanner test-jar - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 961c6aff39a..7d166579c08 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.45.4-SNAPSHOT + 6.46.0 jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 3a7b71545a4..6b80663eead 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.45.4-SNAPSHOT + 6.46.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.45.4-SNAPSHOT + 6.46.0 google-cloud-spanner diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index 7475a8c7906..efbc3323034 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.45.4-SNAPSHOT + 6.46.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.45.4-SNAPSHOT + 6.46.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index e0fd32cded1..07a676637d4 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.45.4-SNAPSHOT + 6.46.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.45.4-SNAPSHOT + 6.46.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index f6ac071fec9..c87f44d3c78 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.45.4-SNAPSHOT + 6.46.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/pom.xml b/pom.xml index 96d018c0dec..0274cdc4543 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.45.4-SNAPSHOT + 6.46.0 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -62,37 +62,37 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.4-SNAPSHOT + 6.46.0 com.google.cloud google-cloud-spanner - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index aa68fa8c1b1..cf0137536c0 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.45.4-SNAPSHOT + 6.46.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.45.4-SNAPSHOT + 6.46.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 35db7a3b902..fb7fc00a3a3 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.45.4-SNAPSHOT + 6.46.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.45.4-SNAPSHOT + 6.46.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index 9f473df9328..666edb481bc 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.45.4-SNAPSHOT + 6.46.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 6d27e3bc059..b5e84778899 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.45.4-SNAPSHOT + 6.46.0 diff --git a/versions.txt b/versions.txt index ea31ae7356c..0b08d5572ee 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.4-SNAPSHOT -proto-google-cloud-spanner-v1:6.45.3:6.45.4-SNAPSHOT -proto-google-cloud-spanner-admin-database-v1:6.45.3:6.45.4-SNAPSHOT -grpc-google-cloud-spanner-v1:6.45.3:6.45.4-SNAPSHOT -grpc-google-cloud-spanner-admin-instance-v1:6.45.3:6.45.4-SNAPSHOT -grpc-google-cloud-spanner-admin-database-v1:6.45.3:6.45.4-SNAPSHOT -google-cloud-spanner:6.45.3:6.45.4-SNAPSHOT -google-cloud-spanner-executor:6.45.3:6.45.4-SNAPSHOT +proto-google-cloud-spanner-admin-instance-v1:6.46.0:6.46.0 +proto-google-cloud-spanner-v1:6.46.0:6.46.0 +proto-google-cloud-spanner-admin-database-v1:6.46.0:6.46.0 +grpc-google-cloud-spanner-v1:6.46.0:6.46.0 +grpc-google-cloud-spanner-admin-instance-v1:6.46.0:6.46.0 +grpc-google-cloud-spanner-admin-database-v1:6.46.0:6.46.0 +google-cloud-spanner:6.46.0:6.46.0 +google-cloud-spanner-executor:6.46.0:6.46.0