From 8060e5b34d1a571b26bd631a86d6a10b9838d9b8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 07:50:12 +0000 Subject: [PATCH 01/15] chore(main): release 6.40.2-SNAPSHOT (#2386) :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 334e33464eb..c72af3981da 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.40.1 + 6.40.2-SNAPSHOT pom com.google.cloud @@ -53,48 +53,48 @@ com.google.cloud google-cloud-spanner - 6.40.1 + 6.40.2-SNAPSHOT com.google.cloud google-cloud-spanner-executor - 6.40.1 + 6.40.2-SNAPSHOT com.google.cloud google-cloud-spanner test-jar - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 28db2caaa8d..52d152f0050 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.40.1 + 6.40.2-SNAPSHOT jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index b2f95ce2bc9..6f77dfeb226 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.40.1 + 6.40.2-SNAPSHOT jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.40.1 + 6.40.2-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 59c6693a105..1ad665c26fe 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.40.1 + 6.40.2-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.40.1 + 6.40.2-SNAPSHOT diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index ed44f80a589..417be82d055 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.40.1 + 6.40.2-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.40.1 + 6.40.2-SNAPSHOT diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 72b7250ac23..0d78faf31db 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.40.1 + 6.40.2-SNAPSHOT grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/pom.xml b/pom.xml index d10cddf9b19..5ea7a2950ae 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.40.1 + 6.40.2-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.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.40.1 + 6.40.2-SNAPSHOT com.google.cloud google-cloud-spanner - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index af69d02a9d0..f5e38b90c1c 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.40.1 + 6.40.2-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.40.1 + 6.40.2-SNAPSHOT diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index fca6f70b069..9c9c77d5364 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.40.1 + 6.40.2-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.40.1 + 6.40.2-SNAPSHOT diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index 9df60a5df8c..d2d1b2cbc87 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.40.1 + 6.40.2-SNAPSHOT proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 803b88b67b1..0ec7b96557b 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.40.1 + 6.40.2-SNAPSHOT diff --git a/versions.txt b/versions.txt index 641b9d0fb9e..6d66441bcf9 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.40.1:6.40.1 -proto-google-cloud-spanner-v1:6.40.1:6.40.1 -proto-google-cloud-spanner-admin-database-v1:6.40.1:6.40.1 -grpc-google-cloud-spanner-v1:6.40.1:6.40.1 -grpc-google-cloud-spanner-admin-instance-v1:6.40.1:6.40.1 -grpc-google-cloud-spanner-admin-database-v1:6.40.1:6.40.1 -google-cloud-spanner:6.40.1:6.40.1 -google-cloud-spanner-executor:6.40.1:6.40.1 +proto-google-cloud-spanner-admin-instance-v1:6.40.1:6.40.2-SNAPSHOT +proto-google-cloud-spanner-v1:6.40.1:6.40.2-SNAPSHOT +proto-google-cloud-spanner-admin-database-v1:6.40.1:6.40.2-SNAPSHOT +grpc-google-cloud-spanner-v1:6.40.1:6.40.2-SNAPSHOT +grpc-google-cloud-spanner-admin-instance-v1:6.40.1:6.40.2-SNAPSHOT +grpc-google-cloud-spanner-admin-database-v1:6.40.1:6.40.2-SNAPSHOT +google-cloud-spanner:6.40.1:6.40.2-SNAPSHOT +google-cloud-spanner-executor:6.40.1:6.40.2-SNAPSHOT From 2693548dd4de6b5b914bbb4de90608386d4aa4e5 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 17 Apr 2023 11:48:35 +0100 Subject: [PATCH 02/15] chore(deps): update dependency com.google.cloud:google-cloud-spanner to v6.40.1 (#2387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:google-cloud-spanner](https://togithub.com/googleapis/java-spanner) | `6.40.0` -> `6.40.1` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner/6.40.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner/6.40.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner/6.40.1/compatibility-slim/6.40.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner/6.40.1/confidence-slim/6.40.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/java-spanner ### [`v6.40.1`](https://togithub.com/googleapis/java-spanner/blob/HEAD/CHANGELOG.md#​6401-httpsgithubcomgoogleapisjava-spannercomparev6400v6401-2023-04-17) [Compare Source](https://togithub.com/googleapis/java-spanner/compare/v6.40.0...v6.40.1) ##### Dependencies - Update dependency com.google.cloud:google-cloud-monitoring to v3.16.0 ([#​2383](https://togithub.com/googleapis/java-spanner/issues/2383)) ([5d5c33a](https://togithub.com/googleapis/java-spanner/commit/5d5c33ae7c01e10112c72777f202187a50b55ac3)) - Update dependency com.google.cloud:google-cloud-trace to v2.15.0 ([#​2384](https://togithub.com/googleapis/java-spanner/issues/2384)) ([6b4ce1f](https://togithub.com/googleapis/java-spanner/commit/6b4ce1fc7ffd837fab6250e36269589d95f5b8c6))
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-spanner). --- README.md | 8 ++++---- samples/install-without-bom/pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 59f82c45492..69efbdfaad2 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.40.0 + 6.40.1 ``` @@ -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.40.0' +implementation 'com.google.cloud:google-cloud-spanner:6.40.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.40.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.40.1" ``` @@ -411,7 +411,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.40.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.40.1 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 3626aa29c27..47eb81f1ed2 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -32,7 +32,7 @@ com.google.cloud google-cloud-spanner - 6.40.0 + 6.40.1 From 02dc53c097bae3f20d7915fecc9c236c4a5f91f9 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 20 Apr 2023 22:50:36 +0200 Subject: [PATCH 03/15] deps: update dependency com.google.api.grpc:proto-google-cloud-spanner-executor-v1 to v1.4.0 (#2395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.api.grpc:proto-google-cloud-spanner-executor-v1](https://togithub.com/googleapis/java-spanner/proto-google-cloud-spanner-executor-v1) ([source](https://togithub.com/googleapis/java-spanner)) | `1.3.0` -> `1.4.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.api.grpc:proto-google-cloud-spanner-executor-v1/1.4.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.api.grpc:proto-google-cloud-spanner-executor-v1/1.4.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.api.grpc:proto-google-cloud-spanner-executor-v1/1.4.0/compatibility-slim/1.3.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.api.grpc:proto-google-cloud-spanner-executor-v1/1.4.0/confidence-slim/1.3.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/java-spanner ### [`v1.4.0`](https://togithub.com/googleapis/java-spanner/compare/1.3.0...1.4.0) [Compare Source](https://togithub.com/googleapis/java-spanner/compare/1.3.0...1.4.0)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-spanner). --- README.md | 2 +- google-cloud-spanner-executor/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69efbdfaad2..47e2effe583 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.12.0') +implementation platform('com.google.cloud:libraries-bom:26.13.0') implementation 'com.google.cloud:google-cloud-spanner' ``` diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 52d152f0050..62038c43c76 100644 --- a/google-cloud-spanner-executor/pom.xml +++ b/google-cloud-spanner-executor/pom.xml @@ -81,7 +81,7 @@ com.google.api.grpc proto-google-cloud-spanner-executor-v1 - 1.3.0 + 1.4.0 com.google.guava From f70dacab3a53dfd7edbd8bec8774fee50d722196 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 21 Apr 2023 08:46:16 +0200 Subject: [PATCH 04/15] chore(deps): update dependency com.google.cloud:libraries-bom to v26.13.0 (#2393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update dependency com.google.cloud:libraries-bom to v26.13.0 * 🦉 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 --- README.md | 2 +- samples/native-image/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 47e2effe583..9961a9759f9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.12.0 + 26.13.0 pom import diff --git a/samples/native-image/pom.xml b/samples/native-image/pom.xml index effe3e9fecb..aa895a139f6 100644 --- a/samples/native-image/pom.xml +++ b/samples/native-image/pom.xml @@ -28,7 +28,7 @@ com.google.cloud libraries-bom - 26.12.0 + 26.13.0 pom import diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index d71f010f086..48cf09bf081 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -33,7 +33,7 @@ com.google.cloud libraries-bom - 26.12.0 + 26.13.0 pom import From 76260eccfc4880c0bfe2987129eced9db5665190 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 21 Apr 2023 08:46:36 +0200 Subject: [PATCH 05/15] build(deps): update dependency org.apache.maven.plugins:maven-project-info-reports-plugin to v3.4.3 (#2392) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ea7a2950ae..60b1905dd00 100644 --- a/pom.xml +++ b/pom.xml @@ -160,7 +160,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.4.2 + 3.4.3 From 5474d40b6d1679f31adf8b8f6ea2d14b0a53c8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 21 Apr 2023 11:47:30 +0200 Subject: [PATCH 06/15] test: untyped nulls as statement parameters (#2388) Add tests to verify that untyped null statement parameters work with both GoogleSQL, PostgreSQL and the emulator. Verifies that https://github.com/GoogleCloudPlatform/cloud-spanner-emulator/issues/31 has been fixed. --- .../google/cloud/spanner/it/ITWriteTest.java | 214 ++++++++++++++---- 1 file changed, 167 insertions(+), 47 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java index 3179093139e..020beb68ef9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import com.google.cloud.ByteArray; import com.google.cloud.Date; @@ -51,6 +52,7 @@ import com.google.cloud.spanner.connection.ConnectionOptions; import com.google.cloud.spanner.testing.EmulatorSpannerHelper; import com.google.common.collect.ImmutableList; +import com.google.protobuf.NullValue; import io.grpc.Context; import java.math.BigDecimal; import java.util.ArrayList; @@ -98,8 +100,7 @@ public static List data() { private static DatabaseClient googleStandardSQLClient; private static DatabaseClient postgreSQLClient; - // TODO: Remove when the emulator supports NUMERIC and JSON - private static final String GOOGLE_STANDARD_SQL_SCHEMA_WITH_NUMERIC_AND_JSON = + private static final String GOOGLE_STANDARD_SQL_SCHEMA = "CREATE TABLE T (" + " K STRING(MAX) NOT NULL," + " BoolValue BOOL," @@ -122,13 +123,14 @@ public static List data() { + " NumericArrayValue ARRAY," + ") PRIMARY KEY (K)"; - private static final String POSTGRESQL_SCHEMA_WITH_NUMERIC = + private static final String POSTGRESQL_SCHEMA = "CREATE TABLE T (" + " K VARCHAR PRIMARY KEY," + " BoolValue BOOL," + " Int64Value BIGINT," + " Float64Value DOUBLE PRECISION," + " StringValue VARCHAR," + + " JsonValue JSONB," + " BytesValue BYTEA," + " TimestampValue TIMESTAMPTZ," + " DateValue DATE," @@ -137,31 +139,13 @@ public static List data() { + " Int64ArrayValue BIGINT[]," + " Float64ArrayValue DOUBLE PRECISION[]," + " StringArrayValue VARCHAR[]," + + " JsonArrayValue JSONB[]," + " BytesArrayValue BYTEA[]," + " TimestampArrayValue TIMESTAMPTZ[]," + " DateArrayValue DATE[]," + " NumericArrayValue NUMERIC[]" + ")"; - private static final String GOOGLE_STANDARD_SQL_SCHEMA_WITHOUT_NUMERIC_AND_JSON = - "CREATE TABLE T (" - + " K STRING(MAX) NOT NULL," - + " BoolValue BOOL," - + " Int64Value INT64," - + " Float64Value FLOAT64," - + " StringValue STRING(MAX)," - + " BytesValue BYTES(MAX)," - + " TimestampValue TIMESTAMP OPTIONS (allow_commit_timestamp = true)," - + " DateValue DATE," - + " BoolArrayValue ARRAY," - + " Int64ArrayValue ARRAY," - + " Float64ArrayValue ARRAY," - + " StringArrayValue ARRAY," - + " BytesArrayValue ARRAY," - + " TimestampArrayValue ARRAY," - + " DateArrayValue ARRAY," - + ") PRIMARY KEY (K)"; - /** Sequence used to generate unique keys. */ private static int seq; @@ -170,21 +154,14 @@ public static List data() { @BeforeClass public static void setUpDatabase() throws ExecutionException, InterruptedException, TimeoutException { - if (EmulatorSpannerHelper.isUsingEmulator()) { - Database googleStandardSQLDatabase = - env.getTestHelper() - .createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA_WITHOUT_NUMERIC_AND_JSON); - - googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); - } else { - Database googleStandardSQLDatabase = - env.getTestHelper().createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA_WITH_NUMERIC_AND_JSON); + Database googleStandardSQLDatabase = + env.getTestHelper().createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA); - googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); + googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); + if (!EmulatorSpannerHelper.isUsingEmulator()) { Database postgreSQLDatabase = env.getTestHelper() - .createTestDatabase( - Dialect.POSTGRESQL, Collections.singletonList(POSTGRESQL_SCHEMA_WITH_NUMERIC)); + .createTestDatabase(Dialect.POSTGRESQL, Collections.singletonList(POSTGRESQL_SCHEMA)); postgreSQLClient = env.getTestHelper().getDatabaseClient(postgreSQLDatabase); } } @@ -396,7 +373,6 @@ public void writeStringNull() { @Test public void writeJson() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json("{\"rating\":9,\"open\":true}")).build()); Struct row = readLastRow("JsonValue"); @@ -407,7 +383,6 @@ public void writeJson() { @Test public void writeJsonEmpty() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json("{}")).build()); Struct row = readLastRow("JsonValue"); @@ -418,7 +393,6 @@ public void writeJsonEmpty() { @Test public void writeJsonNull() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json(null)).build()); Struct row = readLastRow("JsonValue"); @@ -588,7 +562,6 @@ public void writeDateNull() { @Test public void writeNumeric() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericValue").to("3.141592").build()); Struct row = readLastRow("NumericValue"); assertThat(row.isNull(0)).isFalse(); @@ -601,7 +574,6 @@ public void writeNumeric() { @Test public void writeNumericNull() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericValue").to((String) null).build()); Struct row = readLastRow("NumericValue"); assertThat(row.isNull(0)).isTrue(); @@ -751,7 +723,6 @@ public void writeStringArray() { @Test public void writeJsonArrayNull() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(null).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isTrue(); @@ -761,7 +732,6 @@ public void writeJsonArrayNull() { @Test public void writeJsonArrayEmpty() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(Collections.emptyList()).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -772,7 +742,6 @@ public void writeJsonArrayEmpty() { @Test public void writeJsonArray() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(Arrays.asList("[]", null, "{}")).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -783,7 +752,6 @@ public void writeJsonArray() { @Test public void writeJsonArrayNoNulls() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("JsonArrayValue") @@ -878,7 +846,6 @@ public void writeDateArray() { @Test public void writeNumericArrayNull() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericArrayValue").toNumericArray(null).build()); Struct row = readLastRow("NumericArrayValue"); assertThat(row.isNull(0)).isTrue(); @@ -886,7 +853,6 @@ public void writeNumericArrayNull() { @Test public void writeNumericArrayEmpty() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericArrayValue").toNumericArray(ImmutableList.of()).build()); Struct row = readLastRow("NumericArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -899,7 +865,6 @@ public void writeNumericArrayEmpty() { @Test public void writeNumericArray() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("NumericArrayValue") @@ -918,7 +883,6 @@ public void writeNumericArray() { @Test public void writeNumericArrayNoNulls() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("NumericArrayValue") @@ -1013,4 +977,160 @@ public void deadline() { executor.shutdown(); } } + + @Test + public void testWriteUntypedNullValuesGoogleSQL() { + assumeFalse( + "PostgreSQL uses a different parameter format", dialect.dialect == Dialect.POSTGRESQL); + Value untypedNull = + Value.untyped( + com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()); + assertEquals( + Long.valueOf(1L), + client + .readWriteTransaction() + .run( + transaction -> + transaction.executeUpdate( + Statement.newBuilder( + "insert into T (" + + "K," + + "BoolValue," + + "Int64Value," + + "Float64Value," + + "StringValue," + + "JsonValue," + + "BytesValue," + + "TimestampValue," + + "DateValue," + + "NumericValue," + + "BoolArrayValue," + + "Int64ArrayValue," + + "Float64ArrayValue," + + "StringArrayValue," + + "JsonArrayValue," + + "BytesArrayValue," + + "TimestampArrayValue," + + "DateArrayValue," + + "NumericArrayValue" + + ") values (@k, @bool, @int64, @float64, @string, @json, @bytes, @timestamp, @date, @numeric, " + + "@boolArray, @int64Array, @float64Array, @stringArray, @jsonArray, @bytesArray, @timestampArray, @dateArray, @numericArray)") + .bind("k") + .to(uniqueString()) + .bind("bool") + .to(untypedNull) + .bind("int64") + .to(untypedNull) + .bind("float64") + .to(untypedNull) + .bind("string") + .to(untypedNull) + .bind("json") + .to(untypedNull) + .bind("bytes") + .to(untypedNull) + .bind("timestamp") + .to(untypedNull) + .bind("date") + .to(untypedNull) + .bind("numeric") + .to(untypedNull) + .bind("boolArray") + .to(untypedNull) + .bind("int64Array") + .to(untypedNull) + .bind("float64Array") + .to(untypedNull) + .bind("stringArray") + .to(untypedNull) + .bind("jsonArray") + .to(untypedNull) + .bind("bytesArray") + .to(untypedNull) + .bind("timestampArray") + .to(untypedNull) + .bind("dateArray") + .to(untypedNull) + .bind("numericArray") + .to(untypedNull) + .build()))); + } + + @Test + public void testWriteUntypedNullValuesPostgreSQL() { + assumeTrue( + "PostgreSQL uses a different parameter format", dialect.dialect == Dialect.POSTGRESQL); + Value untypedNull = + Value.untyped( + com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()); + assertEquals( + Long.valueOf(1L), + client + .readWriteTransaction() + .run( + transaction -> + transaction.executeUpdate( + Statement.newBuilder( + "insert into T (" + + "K," + + "BoolValue," + + "Int64Value," + + "Float64Value," + + "StringValue," + + "JsonValue," + + "BytesValue," + + "TimestampValue," + + "DateValue," + + "NumericValue," + + "BoolArrayValue," + + "Int64ArrayValue," + + "Float64ArrayValue," + + "StringArrayValue," + + "JsonArrayValue," + + "BytesArrayValue," + + "TimestampArrayValue," + + "DateArrayValue," + + "NumericArrayValue" + + ") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, " + + "$11, $12, $13, $14, $15, $16, $17, $18, $19)") + .bind("p1") + .to(uniqueString()) + .bind("p2") + .to(untypedNull) + .bind("p3") + .to(untypedNull) + .bind("p4") + .to(untypedNull) + .bind("p5") + .to(untypedNull) + .bind("p6") + .to(untypedNull) + .bind("p7") + .to(untypedNull) + .bind("p8") + .to(untypedNull) + .bind("p9") + .to(untypedNull) + .bind("p10") + .to(untypedNull) + .bind("p11") + .to(untypedNull) + .bind("p12") + .to(untypedNull) + .bind("p13") + .to(untypedNull) + .bind("p14") + .to(untypedNull) + .bind("p15") + .to(untypedNull) + .bind("p16") + .to(untypedNull) + .bind("p17") + .to(untypedNull) + .bind("p18") + .to(untypedNull) + .bind("p19") + .to(untypedNull) + .build()))); + } } From b815cb88ff29fb5b9a5d7998e765548244f287c1 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 25 Apr 2023 22:10:29 +0200 Subject: [PATCH 07/15] deps: update dependency com.google.cloud:google-cloud-shared-dependencies to v3.8.0 (#2400) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60b1905dd00..5832d98e1cc 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ UTF-8 github google-cloud-spanner-parent - 3.7.0 + 3.8.0
From e34084d3d5747ca20e0bf198df3576ae89262740 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 25 Apr 2023 22:10:59 +0200 Subject: [PATCH 08/15] build(deps): update dependency org.jacoco:jacoco-maven-plugin to v0.8.10 (#2399) --- google-cloud-spanner/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 6f77dfeb226..3156365549d 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -29,7 +29,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.10 From 8327f210df86bf681ffed6a78ccc9e8fd899c967 Mon Sep 17 00:00:00 2001 From: Roman Slyusarchuk Date: Tue, 25 Apr 2023 13:16:14 -0700 Subject: [PATCH 09/15] feat: add TransactionExecutionOptions support to executor. (#2396) This PR adds support for TransactionExecutionOptions to Cloud Client Executor Framework. --- .../executor/spanner/CloudClientExecutor.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner-executor/src/main/java/com/google/cloud/executor/spanner/CloudClientExecutor.java b/google-cloud-spanner-executor/src/main/java/com/google/cloud/executor/spanner/CloudClientExecutor.java index 55acf44abb2..f4b59505b01 100644 --- a/google-cloud-spanner-executor/src/main/java/com/google/cloud/executor/spanner/CloudClientExecutor.java +++ b/google-cloud-spanner-executor/src/main/java/com/google/cloud/executor/spanner/CloudClientExecutor.java @@ -140,6 +140,7 @@ import com.google.spanner.executor.v1.SpannerAsyncActionResponse; import com.google.spanner.executor.v1.StartBatchTransactionAction; import com.google.spanner.executor.v1.StartTransactionAction; +import com.google.spanner.executor.v1.TransactionExecutionOptions; import com.google.spanner.executor.v1.UpdateCloudBackupAction; import com.google.spanner.executor.v1.UpdateCloudDatabaseDdlAction; import com.google.spanner.executor.v1.UpdateCloudInstanceAction; @@ -227,13 +228,16 @@ private static class ReadWriteTransaction { private Mode finishMode; private SpannerException error; private final String transactionSeed; + private final boolean optimistic; // Set to true when the transaction runner completed, one of these three could happen: runner // committed, abandoned or threw an error. private boolean runnerCompleted; - public ReadWriteTransaction(DatabaseClient dbClient, String transactionSeed) { + public ReadWriteTransaction( + DatabaseClient dbClient, String transactionSeed, boolean optimistic) { this.dbClient = dbClient; this.transactionSeed = transactionSeed; + this.optimistic = optimistic; this.runnerCompleted = false; } @@ -318,7 +322,10 @@ public void startRWTransaction() throws Exception { Runnable runnable = () -> { try { - runner = dbClient.readWriteTransaction(); + runner = + optimistic + ? dbClient.readWriteTransaction(Options.optimisticLock()) + : dbClient.readWriteTransaction(); LOGGER.log(Level.INFO, String.format("Ready to run callable %s\n", transactionSeed)); runner.run(callable); transactionSucceeded(runner.getCommitTimestamp().toProto()); @@ -537,7 +544,8 @@ public synchronized void startReadOnlyTxn( } /** Start a read-write transaction. */ - public synchronized void startReadWriteTxn(DatabaseClient dbClient, Metadata metadata) + public synchronized void startReadWriteTxn( + DatabaseClient dbClient, Metadata metadata, TransactionExecutionOptions options) throws Exception { if ((rwTxn != null) || (roTxn != null) || (batchTxn != null)) { throw SpannerExceptionFactory.newSpannerException( @@ -548,7 +556,7 @@ public synchronized void startReadWriteTxn(DatabaseClient dbClient, Metadata met String.format( "There's no active transaction, safe to create rwTxn: %s\n", getTransactionSeed())); this.metadata = metadata; - rwTxn = new ReadWriteTransaction(dbClient, transactionSeed); + rwTxn = new ReadWriteTransaction(dbClient, transactionSeed, options.getOptimistic()); LOGGER.log( Level.INFO, String.format( @@ -2246,7 +2254,7 @@ private Status executeStartTxn( Level.INFO, "Starting read-write transaction %s\n", executionContext.getTransactionSeed()); - executionContext.startReadWriteTxn(dbClient, metadata); + executionContext.startReadWriteTxn(dbClient, metadata, action.getExecutionOptions()); } executionContext.setDatabaseClient(dbClient); executionContext.initReadState(); From d46097f9f17d9009d211c8c0f16b3e084f8fdbad Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 27 Apr 2023 08:09:12 +0200 Subject: [PATCH 10/15] deps: update dependency com.google.cloud:google-cloud-monitoring to v3.17.0 (#2406) --- samples/install-without-bom/pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 47eb81f1ed2..3d1cd1b23ef 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -23,7 +23,7 @@ UTF-8 0.31.1 2.15.0 - 3.16.0 + 3.17.0
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 0ec7b96557b..e3ddaf008db 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -23,7 +23,7 @@ UTF-8 0.31.1 2.15.0 - 3.16.0 + 3.17.0 From 7993be25e9f380071cded2fa4c2bf630d760a53e Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 27 Apr 2023 09:27:47 +0200 Subject: [PATCH 11/15] deps: update dependency com.google.cloud:google-cloud-trace to v2.16.0 (#2407) --- samples/install-without-bom/pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 3d1cd1b23ef..71c58372966 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -22,7 +22,7 @@ 1.8 UTF-8 0.31.1 - 2.15.0 + 2.16.0 3.17.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index e3ddaf008db..001f912ea11 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -22,7 +22,7 @@ 1.8 UTF-8 0.31.1 - 2.15.0 + 2.16.0 3.17.0 From 8aa7a1dbbf484446ae8eed3cb27d16fc65e6de83 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 27 Apr 2023 19:25:37 +0200 Subject: [PATCH 12/15] deps: update dependency org.junit.vintage:junit-vintage-engine to v5.9.3 (#2401) --- samples/native-image/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/native-image/pom.xml b/samples/native-image/pom.xml index aa895a139f6..b4028d42b65 100644 --- a/samples/native-image/pom.xml +++ b/samples/native-image/pom.xml @@ -103,7 +103,7 @@ org.junit.vintage junit-vintage-engine - 5.9.2 + 5.9.3 test From 85213c8764fcb7fb12df49baaac9bd00e095f269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Thu, 27 Apr 2023 19:30:03 +0200 Subject: [PATCH 13/15] feat: make leak detection configurable for connections (#2405) Make the leak detection and pre-creation of exceptions for session and connection leaks configurable for connections. --- .../spanner/connection/ConnectionImpl.java | 6 ++- .../spanner/connection/ConnectionOptions.java | 49 ++++++++++++++++++- .../connection/ConnectionOptionsTest.java | 45 +++++++++++++++++ 3 files changed, 97 insertions(+), 3 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 824c3f237eb..c208f7b85b7 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 @@ -84,7 +84,7 @@ private LeakedConnectionException() { } } - private volatile LeakedConnectionException leakedException = new LeakedConnectionException(); + private volatile LeakedConnectionException leakedException;; private final SpannerPool spannerPool; private AbstractStatementParser statementParser; /** @@ -222,6 +222,8 @@ static UnitOfWorkType of(TransactionMode transactionMode) { /** Create a connection and register it in the SpannerPool. */ ConnectionImpl(ConnectionOptions options) { Preconditions.checkNotNull(options); + this.leakedException = + options.isTrackConnectionLeaks() ? new LeakedConnectionException() : null; this.statementExecutor = new StatementExecutor(options.getStatementExecutionInterceptors()); this.spannerPool = SpannerPool.INSTANCE; this.options = options; @@ -251,6 +253,8 @@ static UnitOfWorkType of(TransactionMode transactionMode) { Preconditions.checkNotNull(spannerPool); Preconditions.checkNotNull(ddlClient); Preconditions.checkNotNull(dbClient); + this.leakedException = + options.isTrackConnectionLeaks() ? new LeakedConnectionException() : null; this.statementExecutor = new StatementExecutor(Collections.emptyList()); this.spannerPool = spannerPool; this.options = options; 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 e44a99c1ecc..f51ea6efc64 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 @@ -172,6 +172,8 @@ public String[] getValidValues() { private static final RpcPriority DEFAULT_RPC_PRIORITY = null; private static final boolean DEFAULT_RETURN_COMMIT_STATS = false; private static final boolean DEFAULT_LENIENT = false; + private static final boolean DEFAULT_TRACK_SESSION_LEAKS = true; + private static final boolean DEFAULT_TRACK_CONNECTION_LEAKS = true; private static final String PLAIN_TEXT_PROTOCOL = "http:"; private static final String HOST_PROTOCOL = "https:"; @@ -218,6 +220,10 @@ public String[] getValidValues() { private static final String DIALECT_PROPERTY_NAME = "dialect"; /** Name of the 'databaseRole' connection property. */ public static final String DATABASE_ROLE_PROPERTY_NAME = "databaseRole"; + /** Name of the 'trackStackTraceOfSessionCheckout' connection property. */ + public static final String TRACK_SESSION_LEAKS_PROPERTY_NAME = "trackSessionLeaks"; + /** Name of the 'trackStackTraceOfConnectionCreation' connection property. */ + public static final String TRACK_CONNECTION_LEAKS_PROPERTY_NAME = "trackConnectionLeaks"; /** All valid connection properties. */ public static final Set VALID_PROPERTIES = Collections.unmodifiableSet( @@ -287,7 +293,25 @@ public String[] getValidValues() { DIALECT_PROPERTY_NAME, "Sets the dialect to use for 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")))); + "Sets the database role to use for this connection. The default is privileges assigned to IAM role"), + ConnectionProperty.createBooleanProperty( + TRACK_SESSION_LEAKS_PROPERTY_NAME, + "Capture the call stack of the thread that checked out a session of the session pool. This will " + + "pre-create a LeakedSessionException already when a session is checked out. This can be disabled, " + + "for example if a monitoring system logs the pre-created exception. " + + "If disabled, the LeakedSessionException will only be created when an " + + "actual session leak is detected. The stack trace of the exception will " + + "in that case not contain the call stack of when the session was checked out.", + DEFAULT_TRACK_SESSION_LEAKS), + ConnectionProperty.createBooleanProperty( + TRACK_CONNECTION_LEAKS_PROPERTY_NAME, + "Capture the call stack of the thread that created a connection. This will " + + "pre-create a LeakedConnectionException already when a connection is created. " + + "This can be disabled, for example if a monitoring system logs the pre-created exception. " + + "If disabled, the LeakedConnectionException will only be created when an " + + "actual connection leak is detected. The stack trace of the exception will " + + "in that case not contain the call stack of when the connection was created.", + DEFAULT_TRACK_CONNECTION_LEAKS)))); private static final Set INTERNAL_PROPERTIES = Collections.unmodifiableSet( @@ -544,6 +568,8 @@ public static Builder newBuilder() { private final boolean returnCommitStats; private final boolean autoConfigEmulator; private final RpcPriority rpcPriority; + private final boolean trackSessionLeaks; + private final boolean trackConnectionLeaks; private final boolean autocommit; private final boolean readOnly; @@ -588,6 +614,8 @@ private ConnectionOptions(Builder builder) { this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri); this.host = determineHost(matcher, autoConfigEmulator, usePlainText); this.rpcPriority = parseRPCPriority(this.uri); + this.trackSessionLeaks = parseTrackSessionLeaks(this.uri); + this.trackConnectionLeaks = parseTrackConnectionLeaks(this.uri); this.instanceId = matcher.group(Builder.INSTANCE_GROUP); this.databaseName = matcher.group(Builder.DATABASE_GROUP); @@ -641,11 +669,12 @@ private ConnectionOptions(Builder builder) { Collections.unmodifiableList(builder.statementExecutionInterceptors); this.configurator = builder.configurator; - if (this.minSessions != null || this.maxSessions != null) { + if (this.minSessions != null || this.maxSessions != null || !this.trackSessionLeaks) { SessionPoolOptions.Builder sessionPoolOptionsBuilder = builder.sessionPoolOptions == null ? SessionPoolOptions.newBuilder() : builder.sessionPoolOptions.toBuilder(); + sessionPoolOptionsBuilder.setTrackStackTraceOfSessionCheckout(this.trackSessionLeaks); sessionPoolOptionsBuilder.setAutoDetectDialect(true); if (this.minSessions != null) { sessionPoolOptionsBuilder.setMinSessions(this.minSessions); @@ -838,6 +867,18 @@ static boolean parseLenient(String uri) { return value != null ? Boolean.parseBoolean(value) : DEFAULT_LENIENT; } + @VisibleForTesting + static boolean parseTrackSessionLeaks(String uri) { + String value = parseUriProperty(uri, TRACK_SESSION_LEAKS_PROPERTY_NAME); + return value != null ? Boolean.parseBoolean(value) : DEFAULT_TRACK_SESSION_LEAKS; + } + + @VisibleForTesting + static boolean parseTrackConnectionLeaks(String uri) { + String value = parseUriProperty(uri, TRACK_CONNECTION_LEAKS_PROPERTY_NAME); + return value != null ? Boolean.parseBoolean(value) : DEFAULT_TRACK_CONNECTION_LEAKS; + } + @VisibleForTesting static RpcPriority parseRPCPriority(String uri) { String value = parseUriProperty(uri, RPC_PRIORITY_NAME); @@ -1078,6 +1119,10 @@ RpcPriority getRPCPriority() { return rpcPriority; } + boolean isTrackConnectionLeaks() { + return this.trackConnectionLeaks; + } + /** Interceptors that should be executed after each statement */ List getStatementExecutionInterceptors() { return statementExecutionInterceptors; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java index cb862ca3061..ef1ab365577 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -489,6 +490,50 @@ public void testMaxSessions() { assertThat(options.getSessionPoolOptions().getMaxSessions()).isEqualTo(4000); } + @Test + public void testTrackSessionLeaks() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?trackSessionLeaks=false") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertFalse(options.getSessionPoolOptions().isTrackStackTraceOfSessionCheckout()); + } + + @Test + public void testTrackSessionLeaksDefault() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertTrue(options.getSessionPoolOptions().isTrackStackTraceOfSessionCheckout()); + } + + @Test + public void testTrackConnectionLeaks() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?trackConnectionLeaks=false") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertFalse(options.isTrackConnectionLeaks()); + } + + @Test + public void testTrackConnectionLeaksDefault() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertTrue(options.isTrackConnectionLeaks()); + } + @Test public void testLocalConnectionError() { String uri = From 9695acee9195b50e525d87700e86d701b1d9eed2 Mon Sep 17 00:00:00 2001 From: Yifan Zhou <119901292+yifanzyifanz@users.noreply.github.com> Date: Fri, 28 Apr 2023 00:21:56 -0700 Subject: [PATCH 14/15] feat: Leader Aware Routing (#2214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add `x-goog-spanner-route-to-leader` header to Spanner RPC contexts for RW/PDML transactions. The header is added to support leader-aware-routing feature, which aims at reducing cross-regional latency for RW/PDML transactions in a multi-region instance. * feat: Add knob in SpannerOptions to allow users to opt out leader aware routing feature * fix: fix broken tests due to the merge * fix: resolve comments. * fix: resolve comments and add new tests to verify that the route-to-leader header exists for RW transactions and does not exist for RO transactions or when the leader aware routing feature is disabled. * fix: Update comments for SpannerOptions.disableLeaderAwareRouting * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: Disable leader aware routing by default for public preview --------- Co-authored-by: Owl Bot Co-authored-by: Rajat Bhatta <93644539+rajatbhatta@users.noreply.github.com> --- .../clirr-ignored-differences.xml | 99 ++++++ .../cloud/spanner/AbstractReadContext.java | 23 +- .../spanner/PartitionedDmlTransaction.java | 2 +- .../com/google/cloud/spanner/SessionImpl.java | 14 +- .../google/cloud/spanner/SpannerOptions.java | 25 ++ .../cloud/spanner/TransactionRunnerImpl.java | 11 +- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 86 +++-- .../spi/v1/SpannerMetadataProvider.java | 9 +- .../cloud/spanner/spi/v1/SpannerRpc.java | 68 +++- .../cloud/spanner/BatchClientImplTest.java | 3 +- .../PartitionedDmlTransactionTest.java | 23 +- .../google/cloud/spanner/SessionImplTest.java | 18 +- .../google/cloud/spanner/SessionPoolTest.java | 11 +- .../cloud/spanner/SpannerOptionsTest.java | 316 +++++++++--------- .../spanner/TransactionManagerImplTest.java | 17 +- .../spanner/TransactionRunnerImplTest.java | 16 +- .../spanner/spi/v1/GapicSpannerRpcTest.java | 182 ++++++++-- .../spi/v1/SpannerMetadataProviderTest.java | 20 +- 18 files changed, 674 insertions(+), 269 deletions(-) diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 67e9663747c..f158f62ea81 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -222,6 +222,105 @@ com/google/cloud/spanner/connection/Connection com.google.cloud.spanner.ResultSet analyzeUpdateStatement(com.google.cloud.spanner.Statement, com.google.cloud.spanner.ReadContext$QueryAnalyzeMode, com.google.cloud.spanner.Options$UpdateOption[]) + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.spanner.v1.Transaction beginTransaction(com.google.spanner.v1.BeginTransactionRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.api.core.ApiFuture beginTransactionAsync(com.google.spanner.v1.BeginTransactionRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.api.core.ApiFuture beginTransactionAsync(com.google.spanner.v1.BeginTransactionRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.spanner.v1.ResultSet executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + com.google.spanner.v1.ResultSet executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map, boolean) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map, boolean) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.api.core.ApiFuture executeQueryAsync(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall read(com.google.spanner.v1.ReadRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + + + 7005 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map, boolean) + + + 7006 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + com.google.spanner.v1.ResultSet + + + 7006 + com/google/cloud/spanner/spi/v1/GapicSpannerRpc + com.google.spanner.v1.ResultSet executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall + + + 7004 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.spanner.v1.Transaction beginTransaction(com.google.spanner.v1.BeginTransactionRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.api.core.ApiFuture beginTransactionAsync(com.google.spanner.v1.BeginTransactionRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.spanner.v1.ResultSet executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.api.core.ApiFuture executeQueryAsync(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + + + 7004 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall read(com.google.spanner.v1.ReadRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + + + 7005 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map, boolean) + + + 7006 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall executeQuery(com.google.spanner.v1.ExecuteSqlRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map) + com.google.spanner.v1.ResultSet + + + 7006 + com/google/cloud/spanner/spi/v1/SpannerRpc + com.google.spanner.v1.ResultSet executeQuery(com.google.spanner.v1.ExecuteSqlRequest, java.util.Map) + com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall + + 7012 diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java index 7facd19c826..e19ace944a5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java @@ -163,6 +163,11 @@ private SingleReadContext(Builder builder) { this.bound = builder.bound; } + @Override + protected boolean isRouteToLeader() { + return false; + } + @GuardedBy("lock") @Override void beforeReadOrQueryLocked() { @@ -293,6 +298,11 @@ static Builder newBuilder() { } } + @Override + protected boolean isRouteToLeader() { + return false; + } + @Override void beforeReadOrQuery() { super.beforeReadOrQuery(); @@ -347,7 +357,8 @@ void initTransaction() { .setSession(session.getName()) .setOptions(options) .build(); - Transaction transaction = rpc.beginTransaction(request, session.getOptions()); + Transaction transaction = + rpc.beginTransaction(request, session.getOptions(), isRouteToLeader()); if (!transaction.hasReadTimestamp()) { throw SpannerExceptionFactory.newSpannerException( ErrorCode.INTERNAL, "Missing expected transaction.read_timestamp metadata field"); @@ -416,6 +427,10 @@ long getSeqNo() { return seqNo.incrementAndGet(); } + protected boolean isRouteToLeader() { + return false; + } + @Override public final ResultSet read( String table, KeySet keys, Iterable columns, ReadOption... options) { @@ -667,7 +682,8 @@ CloseableIterator startStream(@Nullable ByteString resumeToken request.setTransaction(selector); } SpannerRpc.StreamingCall call = - rpc.executeQuery(request.build(), stream.consumer(), session.getOptions()); + rpc.executeQuery( + request.build(), stream.consumer(), session.getOptions(), isRouteToLeader()); call.request(prefetchChunks); stream.setCall(call, request.getTransaction().hasBegin()); return stream; @@ -798,7 +814,8 @@ CloseableIterator startStream(@Nullable ByteString resumeToken } builder.setRequestOptions(buildRequestOptions(readOptions)); SpannerRpc.StreamingCall call = - rpc.read(builder.build(), stream.consumer(), session.getOptions()); + rpc.read( + builder.build(), stream.consumer(), session.getOptions(), isRouteToLeader()); call.request(prefetchChunks); stream.setCall(call, /* withBeginTransaction = */ builder.getTransaction().hasBegin()); return stream; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java index 976f6136db7..36991b18c3d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java @@ -202,7 +202,7 @@ private ByteString initTransaction() { TransactionOptions.newBuilder() .setPartitionedDml(TransactionOptions.PartitionedDml.getDefaultInstance())) .build(); - Transaction tx = rpc.beginTransaction(request, session.getOptions()); + Transaction tx = rpc.beginTransaction(request, session.getOptions(), true); if (tx.getId().isEmpty()) { throw SpannerExceptionFactory.newSpannerException( ErrorCode.INTERNAL, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java index df6163e93e9..2bef8e3ada4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java @@ -275,7 +275,7 @@ public AsyncTransactionManagerImpl transactionManagerAsync(TransactionOption... @Override public void prepareReadWriteTransaction() { setActive(null); - readyTransactionId = beginTransaction(); + readyTransactionId = beginTransaction(true); } @Override @@ -296,9 +296,9 @@ public void close() { } } - ByteString beginTransaction() { + ByteString beginTransaction(boolean routeToLeader) { try { - return beginTransactionAsync().get(); + return beginTransactionAsync(routeToLeader).get(); } catch (ExecutionException e) { throw SpannerExceptionFactory.newSpannerException(e.getCause() == null ? e : e.getCause()); } catch (InterruptedException e) { @@ -306,11 +306,11 @@ ByteString beginTransaction() { } } - ApiFuture beginTransactionAsync() { - return beginTransactionAsync(Options.fromTransactionOptions()); + ApiFuture beginTransactionAsync(boolean routeToLeader) { + return beginTransactionAsync(Options.fromTransactionOptions(), routeToLeader); } - ApiFuture beginTransactionAsync(Options transactionOptions) { + ApiFuture beginTransactionAsync(Options transactionOptions, boolean routeToLeader) { final SettableApiFuture res = SettableApiFuture.create(); final Span span = tracer.spanBuilder(SpannerImpl.BEGIN_TRANSACTION).startSpan(); final BeginTransactionRequest request = @@ -319,7 +319,7 @@ ApiFuture beginTransactionAsync(Options transactionOptions) { .setOptions(createReadWriteTransactionOptions(transactionOptions)) .build(); final ApiFuture requestFuture = - spanner.getRpc().beginTransactionAsync(request, options); + spanner.getRpc().beginTransactionAsync(request, options, routeToLeader); requestFuture.addListener( tracer.withSpan( span, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index d6243f59592..6663d3f8e68 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -132,6 +132,7 @@ public class SpannerOptions extends ServiceOptions { private final CallCredentialsProvider callCredentialsProvider; private final CloseableExecutorProvider asyncExecutorProvider; private final String compressorName; + private final boolean leaderAwareRoutingEnabled; /** * Interface that can be used to provide {@link CallCredentials} instead of {@link Credentials} to @@ -600,6 +601,7 @@ private SpannerOptions(Builder builder) { callCredentialsProvider = builder.callCredentialsProvider; asyncExecutorProvider = builder.asyncExecutorProvider; compressorName = builder.compressorName; + leaderAwareRoutingEnabled = builder.leaderAwareRoutingEnabled; } /** @@ -700,6 +702,7 @@ public static class Builder private CloseableExecutorProvider asyncExecutorProvider; private String compressorName; private String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST"); + private boolean leaderAwareRoutingEnabled = false; private Builder() { // Manually set retry and polling settings that work. @@ -1155,6 +1158,24 @@ public Builder setEmulatorHost(String emulatorHost) { return this; } + /** + * Enable leader aware routing. Leader aware routing would route all requests in RW/PDML + * transactions to the leader region. + */ + public Builder enableLeaderAwareRouting() { + this.leaderAwareRoutingEnabled = true; + return this; + } + + /** + * Disable leader aware routing. Disabling leader aware routing would route all requests in + * RW/PDML transactions to any region. + */ + public Builder disableLeaderAwareRouting() { + this.leaderAwareRoutingEnabled = false; + return this; + } + @SuppressWarnings("rawtypes") @Override public SpannerOptions build() { @@ -1291,6 +1312,10 @@ public String getCompressorName() { return compressorName; } + public boolean isLeaderAwareRoutingEnabled() { + return leaderAwareRoutingEnabled; + } + /** Returns the default query options to use for the specific database. */ public QueryOptions getDefaultQueryOptions(DatabaseId databaseId) { // Use the specific query options for the database if any have been specified. These have diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java index 3d3b34c4c39..ef937e993bd 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java @@ -198,6 +198,11 @@ private TransactionContextImpl(Builder builder) { this.finishedAsyncOperations.set(null); } + @Override + protected boolean isRouteToLeader() { + return true; + } + private void increaseAsyncOperations() { synchronized (lock) { if (runningAsyncOperations == 0) { @@ -255,7 +260,7 @@ ApiFuture ensureTxnAsync() { private void createTxnAsync(final SettableApiFuture res) { span.addAnnotation("Creating Transaction"); - final ApiFuture fut = session.beginTransactionAsync(options); + final ApiFuture fut = session.beginTransactionAsync(options, isRouteToLeader()); fut.addListener( () -> { try { @@ -717,7 +722,7 @@ private ResultSet internalExecuteUpdate( /* withTransactionSelector = */ true); try { com.google.spanner.v1.ResultSet resultSet = - rpc.executeQuery(builder.build(), session.getOptions()); + rpc.executeQuery(builder.build(), session.getOptions(), isRouteToLeader()); if (resultSet.getMetadata().hasTransaction()) { onTransactionMetadata( resultSet.getMetadata().getTransaction(), builder.getTransaction().hasBegin()); @@ -747,7 +752,7 @@ public ApiFuture executeUpdateAsync(Statement statement, UpdateOption... o // Register the update as an async operation that must finish before the transaction may // commit. increaseAsyncOperations(); - resultSet = rpc.executeQueryAsync(builder.build(), session.getOptions()); + resultSet = rpc.executeQueryAsync(builder.build(), session.getOptions(), isRouteToLeader()); } catch (Throwable t) { decreaseAsyncOperations(); throw t; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index eb8633d0449..4d70a26866c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -252,6 +252,7 @@ public class GapicSpannerRpc implements SpannerRpc { private static final double ADMINISTRATIVE_REQUESTS_RATE_LIMIT = 1.0D; private static final ConcurrentMap ADMINISTRATIVE_REQUESTS_RATE_LIMITERS = new ConcurrentHashMap<>(); + private final boolean leaderAwareRoutingEnabled; public static GapicSpannerRpc create(SpannerOptions options) { return new GapicSpannerRpc(options); @@ -302,6 +303,7 @@ public GapicSpannerRpc(final SpannerOptions options) { internalHeaderProviderBuilder.getResourceHeaderKey()); this.callCredentialsProvider = options.getCallCredentialsProvider(); this.compressorName = options.getCompressorName(); + this.leaderAwareRoutingEnabled = options.isLeaderAwareRoutingEnabled(); if (initializeStubs) { // First check if SpannerOptions provides a TransportChannelProvider. Create one @@ -1527,7 +1529,8 @@ public List batchCreateSessions( requestBuilder.setSessionTemplate(sessionBuilder); BatchCreateSessionsRequest request = requestBuilder.build(); GrpcCallContext context = - newCallContext(options, databaseName, request, SpannerGrpc.getBatchCreateSessionsMethod()); + newCallContext( + options, databaseName, request, SpannerGrpc.getBatchCreateSessionsMethod(), true); return get(spannerStub.batchCreateSessionsCallable().futureCall(request, context)) .getSessionList(); } @@ -1551,7 +1554,7 @@ public Session createSession( requestBuilder.setSession(sessionBuilder); CreateSessionRequest request = requestBuilder.build(); GrpcCallContext context = - newCallContext(options, databaseName, request, SpannerGrpc.getCreateSessionMethod()); + newCallContext(options, databaseName, request, SpannerGrpc.getCreateSessionMethod(), true); return get(spannerStub.createSessionCallable().futureCall(request, context)); } @@ -1571,9 +1574,13 @@ public ApiFuture asyncDeleteSession(String sessionName, @Nullable Map options) { + ReadRequest request, + ResultStreamConsumer consumer, + @Nullable Map options, + boolean routeToLeader) { GrpcCallContext context = - newCallContext(options, request.getSession(), request, SpannerGrpc.getReadMethod()); + newCallContext( + options, request.getSession(), request, SpannerGrpc.getReadMethod(), routeToLeader); SpannerResponseObserver responseObserver = new SpannerResponseObserver(consumer); spannerStub.streamingReadCallable().call(request, responseObserver, context); final StreamController controller = responseObserver.getController(); @@ -1593,15 +1600,21 @@ public void cancel(String message) { } @Override - public ResultSet executeQuery(ExecuteSqlRequest request, @Nullable Map options) { - return get(executeQueryAsync(request, options)); + public ResultSet executeQuery( + ExecuteSqlRequest request, @Nullable Map options, boolean routeToLeader) { + return get(executeQueryAsync(request, options, routeToLeader)); } @Override public ApiFuture executeQueryAsync( - ExecuteSqlRequest request, @Nullable Map options) { + ExecuteSqlRequest request, @Nullable Map options, boolean routeToLeader) { GrpcCallContext context = - newCallContext(options, request.getSession(), request, SpannerGrpc.getExecuteSqlMethod()); + newCallContext( + options, + request.getSession(), + request, + SpannerGrpc.getExecuteSqlMethod(), + routeToLeader); return spannerStub.executeSqlCallable().futureCall(request, context); } @@ -1609,7 +1622,8 @@ public ApiFuture executeQueryAsync( public ResultSet executePartitionedDml( ExecuteSqlRequest request, @Nullable Map options) { GrpcCallContext context = - newCallContext(options, request.getSession(), request, SpannerGrpc.getExecuteSqlMethod()); + newCallContext( + options, request.getSession(), request, SpannerGrpc.getExecuteSqlMethod(), true); return get(partitionedDmlStub.executeSqlCallable().futureCall(request, context)); } @@ -1623,7 +1637,11 @@ public ServerStream executeStreamingPartitionedDml( ExecuteSqlRequest request, Map options, Duration timeout) { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getExecuteStreamingSqlMethod()); + options, + request.getSession(), + request, + SpannerGrpc.getExecuteStreamingSqlMethod(), + true); // Override any timeout settings that might have been set on the call context. context = context.withTimeout(timeout).withStreamWaitTimeout(timeout); return partitionedDmlStub.executeStreamingSqlCallable().call(request, context); @@ -1631,10 +1649,17 @@ public ServerStream executeStreamingPartitionedDml( @Override public StreamingCall executeQuery( - ExecuteSqlRequest request, ResultStreamConsumer consumer, @Nullable Map options) { + ExecuteSqlRequest request, + ResultStreamConsumer consumer, + @Nullable Map options, + boolean routeToLeader) { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getExecuteStreamingSqlMethod()); + options, + request.getSession(), + request, + SpannerGrpc.getExecuteStreamingSqlMethod(), + routeToLeader); SpannerResponseObserver responseObserver = new SpannerResponseObserver(consumer); spannerStub.executeStreamingSqlCallable().call(request, responseObserver, context); final StreamController controller = responseObserver.getController(); @@ -1664,30 +1689,35 @@ public ApiFuture executeBatchDmlAsync( ExecuteBatchDmlRequest request, @Nullable Map options) { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getExecuteBatchDmlMethod()); + options, request.getSession(), request, SpannerGrpc.getExecuteBatchDmlMethod(), true); return spannerStub.executeBatchDmlCallable().futureCall(request, context); } @Override public ApiFuture beginTransactionAsync( - BeginTransactionRequest request, @Nullable Map options) { + BeginTransactionRequest request, @Nullable Map options, boolean routeToLeader) { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getBeginTransactionMethod()); + options, + request.getSession(), + request, + SpannerGrpc.getBeginTransactionMethod(), + routeToLeader); return spannerStub.beginTransactionCallable().futureCall(request, context); } @Override public Transaction beginTransaction( - BeginTransactionRequest request, @Nullable Map options) throws SpannerException { - return get(beginTransactionAsync(request, options)); + BeginTransactionRequest request, @Nullable Map options, boolean routeToLeader) + throws SpannerException { + return get(beginTransactionAsync(request, options, routeToLeader)); } @Override public ApiFuture commitAsync( CommitRequest request, @Nullable Map options) { GrpcCallContext context = - newCallContext(options, request.getSession(), request, SpannerGrpc.getCommitMethod()); + newCallContext(options, request.getSession(), request, SpannerGrpc.getCommitMethod(), true); return spannerStub.commitCallable().futureCall(request, context); } @@ -1700,7 +1730,8 @@ public CommitResponse commit(CommitRequest commitRequest, @Nullable Map rollbackAsync(RollbackRequest request, @Nullable Map options) { GrpcCallContext context = - newCallContext(options, request.getSession(), request, SpannerGrpc.getRollbackMethod()); + newCallContext( + options, request.getSession(), request, SpannerGrpc.getRollbackMethod(), true); return spannerStub.rollbackCallable().futureCall(request, context); } @@ -1715,7 +1746,7 @@ public PartitionResponse partitionQuery( PartitionQueryRequest request, @Nullable Map options) throws SpannerException { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getPartitionQueryMethod()); + options, request.getSession(), request, SpannerGrpc.getPartitionQueryMethod(), true); return get(spannerStub.partitionQueryCallable().futureCall(request, context)); } @@ -1724,7 +1755,7 @@ public PartitionResponse partitionRead( PartitionReadRequest request, @Nullable Map options) throws SpannerException { GrpcCallContext context = newCallContext( - options, request.getSession(), request, SpannerGrpc.getPartitionReadMethod()); + options, request.getSession(), request, SpannerGrpc.getPartitionReadMethod(), true); return get(spannerStub.partitionReadCallable().futureCall(request, context)); } @@ -1833,6 +1864,16 @@ GrpcCallContext newCallContext( String resource, ReqT request, MethodDescriptor method) { + return newCallContext(options, resource, request, method, false); + } + + @VisibleForTesting + GrpcCallContext newCallContext( + @Nullable Map options, + String resource, + ReqT request, + MethodDescriptor method, + boolean routeToLeader) { GrpcCallContext context = GrpcCallContext.createDefault(); if (options != null) { context = context.withChannelAffinity(Option.CHANNEL_HINT.getLong(options).intValue()); @@ -1842,6 +1883,9 @@ GrpcCallContext newCallContext( context = context.withCallOptions(context.getCallOptions().withCompression(compressorName)); } context = context.withExtraHeaders(metadataProvider.newExtraHeaders(resource, projectName)); + if (routeToLeader && leaderAwareRoutingEnabled) { + context = context.withExtraHeaders(metadataProvider.newRouteToLeaderHeader()); + } if (callCredentialsProvider != null) { CallCredentials callCredentials = callCredentialsProvider.getCallCredentials(); if (callCredentials != null) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java index 7f9a32765e0..77406a5399b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java @@ -28,12 +28,15 @@ class SpannerMetadataProvider { private final Map, String> headers; private final String resourceHeaderKey; - + private static final String ROUTE_TO_LEADER_HEADER_KEY = "x-goog-spanner-route-to-leader"; private static final Pattern[] RESOURCE_TOKEN_PATTERNS = { Pattern.compile("^(?projects/[^/]*/instances/[^/]*/databases/[^/]*)(.*)?"), Pattern.compile("^(?projects/[^/]*/instances/[^/]*)(.*)?") }; + private static final Map> ROUTE_TO_LEADER_HEADER_MAP = + ImmutableMap.of(ROUTE_TO_LEADER_HEADER_KEY, Collections.singletonList("true")); + private SpannerMetadataProvider(Map headers, String resourceHeaderKey) { this.resourceHeaderKey = resourceHeaderKey; this.headers = constructHeadersAsMetadata(headers); @@ -66,6 +69,10 @@ Map> newExtraHeaders( .build(); } + Map> newRouteToLeaderHeader() { + return ROUTE_TO_LEADER_HEADER_MAP; + } + private Map, String> constructHeadersAsMetadata( Map headers) { ImmutableMap.Builder, String> headersAsMetadataBuilder = diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java index 2f68b9c1df2..552c53e85ee 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java @@ -319,13 +319,41 @@ Session createSession( ApiFuture asyncDeleteSession(String sessionName, @Nullable Map options) throws SpannerException; + /** + * Performs a streaming read. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ StreamingCall read( - ReadRequest request, ResultStreamConsumer consumer, @Nullable Map options); + ReadRequest request, + ResultStreamConsumer consumer, + @Nullable Map options, + boolean routeToLeader); - ResultSet executeQuery(ExecuteSqlRequest request, @Nullable Map options); + /** + * Executes a query. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ + ResultSet executeQuery( + ExecuteSqlRequest request, @Nullable Map options, boolean routeToLeader); + /** + * Executes a query asynchronously. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ ApiFuture executeQueryAsync( - ExecuteSqlRequest request, @Nullable Map options); + ExecuteSqlRequest request, @Nullable Map options, boolean routeToLeader); ResultSet executePartitionedDml(ExecuteSqlRequest request, @Nullable Map options); @@ -334,19 +362,47 @@ ApiFuture executeQueryAsync( ServerStream executeStreamingPartitionedDml( ExecuteSqlRequest request, @Nullable Map options, Duration timeout); + /** + * Executes a query with streaming result. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ StreamingCall executeQuery( - ExecuteSqlRequest request, ResultStreamConsumer consumer, @Nullable Map options); + ExecuteSqlRequest request, + ResultStreamConsumer consumer, + @Nullable Map options, + boolean routeToLeader); ExecuteBatchDmlResponse executeBatchDml(ExecuteBatchDmlRequest build, Map options); ApiFuture executeBatchDmlAsync( ExecuteBatchDmlRequest build, Map options); - Transaction beginTransaction(BeginTransactionRequest request, @Nullable Map options) + /** + * Begins a transaction. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ + Transaction beginTransaction( + BeginTransactionRequest request, @Nullable Map options, boolean routeToLeader) throws SpannerException; + /** + * Begins a transaction asynchronously. + * + * @param routeToLeader Set to true to route the request to the leader region, and false to route + * the request to any region. When leader aware routing is enabled, RW/PDML requests are + * preferred to be routed to the leader region, and RO requests (except for + * PartitionRead/PartitionQuery) are preferred to be routed to any region for optimal latency. + */ ApiFuture beginTransactionAsync( - BeginTransactionRequest request, @Nullable Map options); + BeginTransactionRequest request, @Nullable Map options, boolean routeToLeader); CommitResponse commit(CommitRequest commitRequest, @Nullable Map options) throws SpannerException; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java index 2c29b87f86b..18ae8a07b35 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java @@ -91,7 +91,8 @@ public void testBatchReadOnlyTxnWithBound() throws Exception { com.google.protobuf.Timestamp timestamp = Timestamps.parse(TIMESTAMP); Transaction txnMetadata = Transaction.newBuilder().setId(TXN_ID).setReadTimestamp(timestamp).build(); - when(gapicRpc.beginTransaction(Mockito.any(), optionsCaptor.capture())).thenReturn(txnMetadata); + when(gapicRpc.beginTransaction(Mockito.any(), optionsCaptor.capture(), eq(false))) + .thenReturn(txnMetadata); BatchReadOnlyTransaction batchTxn = client.batchReadOnlyTransaction(TimestampBound.strong()); assertThat(batchTxn.getBatchTransactionId().getSessionId()).isEqualTo(SESSION_NAME); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/PartitionedDmlTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/PartitionedDmlTransactionTest.java index de1ec8fa39e..93e0e3eb3d0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/PartitionedDmlTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/PartitionedDmlTransactionTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyMap; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -97,7 +98,7 @@ public void setup() { MockitoAnnotations.initMocks(this); when(session.getName()).thenReturn(sessionId); when(session.getOptions()).thenReturn(Collections.EMPTY_MAP); - when(rpc.beginTransaction(any(BeginTransactionRequest.class), anyMap())) + when(rpc.beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true))) .thenReturn(Transaction.newBuilder().setId(txId).build()); tx = new PartitionedDmlTransaction(session, rpc, ticker); @@ -117,7 +118,7 @@ public void testExecuteStreamingPartitionedUpdate() { long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10)); assertThat(count).isEqualTo(1000L); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -139,7 +140,7 @@ public void testExecuteStreamingPartitionedUpdateWithUpdateOptions() { Statement.of(sql), Duration.ofMinutes(10), Options.tag(tag)); assertThat(count).isEqualTo(1000L); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithRequestOptions), anyMap(), any(Duration.class)); @@ -168,7 +169,7 @@ public void testExecuteStreamingPartitionedUpdateAborted() { long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10)); assertThat(count).isEqualTo(1000L); - verify(rpc, times(2)).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc, times(2)).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc, times(2)) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -200,7 +201,7 @@ public void testExecuteStreamingPartitionedUpdateUnavailable() { long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10)); assertThat(count).isEqualTo(1000L); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -231,7 +232,7 @@ public void testExecuteStreamingPartitionedUpdateUnavailableAndThenDeadlineExcee SpannerException.class, () -> tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10))); assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -259,7 +260,7 @@ public void testExecuteStreamingPartitionedUpdateAbortedAndThenDeadlineExceeded( SpannerException.class, () -> tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10))); assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - verify(rpc, times(2)).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc, times(2)).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -297,7 +298,7 @@ public Long answer(InvocationOnMock invocation) { () -> tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10))); assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); // It should start a transaction exactly 10 times (10 ticks == 10 minutes). - verify(rpc, times(10)).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc, times(10)).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); // The last transaction should timeout before it starts the actual statement execution, which // means that the execute method is only executed 9 times. verify(rpc, times(9)) @@ -335,7 +336,7 @@ public void testExecuteStreamingPartitionedUpdateUnexpectedEOS() { long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10)); assertThat(count).isEqualTo(1000L); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -374,7 +375,7 @@ public void testExecuteStreamingPartitionedUpdateRSTstream() { long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10)); assertThat(count).isEqualTo(1000L); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); @@ -405,7 +406,7 @@ public void testExecuteStreamingPartitionedUpdateGenericInternalException() { SpannerException.class, () -> tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10))); assertEquals(ErrorCode.INTERNAL, e.getErrorCode()); - verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap()); + verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)); verify(rpc) .executeStreamingPartitionedDml( Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionImplTest.java index 1174cbf4ebd..90e9a684d95 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionImplTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -100,7 +101,9 @@ public void setUp() { Transaction txn = Transaction.newBuilder().setId(ByteString.copyFromUtf8("TEST")).build(); Mockito.when( rpc.beginTransactionAsync( - Mockito.any(BeginTransactionRequest.class), Mockito.any(Map.class))) + Mockito.any(BeginTransactionRequest.class), + Mockito.any(Map.class), + Mockito.anyBoolean())) .thenReturn(ApiFutures.immediateFuture(txn)); CommitResponse commitResponse = CommitResponse.newBuilder() @@ -350,7 +353,7 @@ public void singleUseContextClosesTransaction() { public void prepareClosesOldSingleUseContext() { ReadContext ctx = session.singleUse(TimestampBound.strong()); - Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options))) + Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options), eq(false))) .thenReturn(Transaction.newBuilder().setId(ByteString.copyFromUtf8("t1")).build()); session.prepareReadWriteTransaction(); IllegalStateException e = @@ -414,7 +417,7 @@ public void request(int numMessages) {} private void mockRead(final PartialResultSet myResultSet) { final ArgumentCaptor consumer = ArgumentCaptor.forClass(SpannerRpc.ResultStreamConsumer.class); - Mockito.when(rpc.read(Mockito.any(), consumer.capture(), Mockito.eq(options))) + Mockito.when(rpc.read(Mockito.any(), consumer.capture(), Mockito.eq(options), eq(false))) .then( invocation -> { consumer.getValue().onPartialResultSet(myResultSet); @@ -430,7 +433,8 @@ public void multiUseReadOnlyTransactionReturnsEmptyTransactionMetadata() { PartialResultSet.newBuilder() .setMetadata(newMetadata(Type.struct(Type.StructField.of("C", Type.string())))) .build(); - Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options))).thenReturn(txnMetadata); + Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options), eq(false))) + .thenReturn(txnMetadata); mockRead(resultSet); ReadOnlyTransaction txn = session.readOnlyTransaction(TimestampBound.strong()); @@ -448,7 +452,8 @@ public void multiUseReadOnlyTransactionReturnsMissingTimestamp() { PartialResultSet.newBuilder() .setMetadata(newMetadata(Type.struct(Type.StructField.of("C", Type.string())))) .build(); - Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options))).thenReturn(txnMetadata); + Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options), eq(false))) + .thenReturn(txnMetadata); mockRead(resultSet); ReadOnlyTransaction txn = session.readOnlyTransaction(TimestampBound.strong()); @@ -467,7 +472,8 @@ public void multiUseReadOnlyTransactionReturnsMissingTransactionId() throws Pars PartialResultSet.newBuilder() .setMetadata(newMetadata(Type.struct(Type.StructField.of("C", Type.string())))) .build(); - Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options))).thenReturn(txnMetadata); + Mockito.when(rpc.beginTransaction(Mockito.any(), Mockito.eq(options), eq(false))) + .thenReturn(txnMetadata); mockRead(resultSet); ReadOnlyTransaction txn = session.readOnlyTransaction(TimestampBound.strong()); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java index 4e5b1e0395c..1f7391a60f9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java @@ -763,9 +763,12 @@ public void testSessionNotFoundReadWriteTransaction() { when(rpc.asyncDeleteSession(Mockito.anyString(), Mockito.anyMap())) .thenReturn(ApiFutures.immediateFuture(Empty.getDefaultInstance())); when(rpc.executeQuery( - any(ExecuteSqlRequest.class), any(ResultStreamConsumer.class), any(Map.class))) + any(ExecuteSqlRequest.class), + any(ResultStreamConsumer.class), + any(Map.class), + eq(true))) .thenReturn(closedStreamingCall); - when(rpc.executeQuery(any(ExecuteSqlRequest.class), any(Map.class))) + when(rpc.executeQuery(any(ExecuteSqlRequest.class), any(Map.class), eq(true))) .thenThrow(sessionNotFound); when(rpc.executeBatchDml(any(ExecuteBatchDmlRequest.class), any(Map.class))) .thenThrow(sessionNotFound); @@ -786,7 +789,7 @@ public void testSessionNotFoundReadWriteTransaction() { .thenReturn(ApiFutures.immediateFuture(Empty.getDefaultInstance())); when(closedSession.newTransaction(Options.fromTransactionOptions())) .thenReturn(closedTransactionContext); - when(closedSession.beginTransactionAsync(any())).thenThrow(sessionNotFound); + when(closedSession.beginTransactionAsync(any(), eq(true))).thenThrow(sessionNotFound); TransactionRunnerImpl closedTransactionRunner = new TransactionRunnerImpl(closedSession); closedTransactionRunner.setSpan(mock(Span.class)); when(closedSession.readWriteTransaction()).thenReturn(closedTransactionRunner); @@ -799,7 +802,7 @@ public void testSessionNotFoundReadWriteTransaction() { final TransactionContextImpl openTransactionContext = mock(TransactionContextImpl.class); when(openSession.newTransaction(Options.fromTransactionOptions())) .thenReturn(openTransactionContext); - when(openSession.beginTransactionAsync(any())) + when(openSession.beginTransactionAsync(any(), eq(true))) .thenReturn(ApiFutures.immediateFuture(ByteString.copyFromUtf8("open-txn"))); TransactionRunnerImpl openTransactionRunner = new TransactionRunnerImpl(openSession); openTransactionRunner.setSpan(mock(Span.class)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 8819dab462b..03844a6b105 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -20,9 +20,13 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import com.google.api.gax.grpc.GrpcCallContext; @@ -82,7 +86,7 @@ public void defaultBuilder() { assertThat(options.getHost()).isEqualTo("http://" + System.getenv("SPANNER_EMULATOR_HOST")); } assertThat(options.getPrefetchChunks()).isEqualTo(4); - assertThat(options.getSessionLabels()).isNull(); + assertNull(options.getSessionLabels()); } @Test @@ -453,7 +457,7 @@ public void testInvalidTransport() { () -> SpannerOptions.newBuilder() .setTransportOptions(Mockito.mock(TransportOptions.class))); - assertThat(e.getMessage()).isNotNull(); + assertNotNull(e.getMessage()); } @Test @@ -463,7 +467,7 @@ public void testInvalidSessionLabels() { NullPointerException e = assertThrows( NullPointerException.class, () -> SpannerOptions.newBuilder().setSessionLabels(labels)); - assertThat(e.getMessage()).isNotNull(); + assertNotNull(e.getMessage()); } @Test @@ -471,7 +475,7 @@ public void testNullSessionLabels() { NullPointerException e = assertThrows( NullPointerException.class, () -> SpannerOptions.newBuilder().setSessionLabels(null)); - assertThat(e.getMessage()).isNotNull(); + assertNotNull(e.getMessage()); } @Test @@ -664,120 +668,119 @@ public void testCompressorName() { .build() .getCompressorName()) .isEqualTo("identity"); - assertThat( - SpannerOptions.newBuilder() - .setProjectId("p") - .setCompressorName(null) - .build() - .getCompressorName()) - .isNull(); + assertNull( + SpannerOptions.newBuilder() + .setProjectId("p") + .setCompressorName(null) + .build() + .getCompressorName()); assertThrows( IllegalArgumentException.class, () -> SpannerOptions.newBuilder().setCompressorName("foo")); } + @Test + public void testLeaderAwareRoutingEnablement() { + assertFalse( + SpannerOptions.newBuilder().setProjectId("p").build().isLeaderAwareRoutingEnabled()); + assertTrue( + SpannerOptions.newBuilder() + .setProjectId("p") + .enableLeaderAwareRouting() + .build() + .isLeaderAwareRoutingEnabled()); + assertFalse( + SpannerOptions.newBuilder() + .setProjectId("p") + .disableLeaderAwareRouting() + .build() + .isLeaderAwareRoutingEnabled()); + } + @Test public void testSpannerCallContextTimeoutConfigurator_NullValues() { SpannerCallContextTimeoutConfigurator configurator = SpannerCallContextTimeoutConfigurator.create(); ApiCallContext inputCallContext = GrpcCallContext.createDefault(); - assertThat( - configurator.configure( - inputCallContext, - BatchCreateSessionsRequest.getDefaultInstance(), - SpannerGrpc.getBatchCreateSessionsMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - CreateSessionRequest.getDefaultInstance(), - SpannerGrpc.getCreateSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - DeleteSessionRequest.getDefaultInstance(), - SpannerGrpc.getDeleteSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - GetSessionRequest.getDefaultInstance(), - SpannerGrpc.getGetSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - DeleteSessionRequest.getDefaultInstance(), - SpannerGrpc.getDeleteSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - ListSessionsRequest.getDefaultInstance(), - SpannerGrpc.getListSessionsMethod())) - .isNull(); - - assertThat( - configurator.configure( - inputCallContext, - BeginTransactionRequest.getDefaultInstance(), - SpannerGrpc.getBeginTransactionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - CommitRequest.getDefaultInstance(), - SpannerGrpc.getCommitMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - RollbackRequest.getDefaultInstance(), - SpannerGrpc.getRollbackMethod())) - .isNull(); - - assertThat( - configurator.configure( - inputCallContext, - ExecuteSqlRequest.getDefaultInstance(), - SpannerGrpc.getExecuteSqlMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - ExecuteSqlRequest.getDefaultInstance(), - SpannerGrpc.getExecuteStreamingSqlMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - ExecuteBatchDmlRequest.getDefaultInstance(), - SpannerGrpc.getExecuteBatchDmlMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, ReadRequest.getDefaultInstance(), SpannerGrpc.getReadMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - ReadRequest.getDefaultInstance(), - SpannerGrpc.getStreamingReadMethod())) - .isNull(); - - assertThat( - configurator.configure( - inputCallContext, - PartitionQueryRequest.getDefaultInstance(), - SpannerGrpc.getPartitionQueryMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - PartitionReadRequest.getDefaultInstance(), - SpannerGrpc.getPartitionReadMethod())) - .isNull(); + assertNull( + configurator.configure( + inputCallContext, + BatchCreateSessionsRequest.getDefaultInstance(), + SpannerGrpc.getBatchCreateSessionsMethod())); + assertNull( + configurator.configure( + inputCallContext, + CreateSessionRequest.getDefaultInstance(), + SpannerGrpc.getCreateSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + DeleteSessionRequest.getDefaultInstance(), + SpannerGrpc.getDeleteSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + GetSessionRequest.getDefaultInstance(), + SpannerGrpc.getGetSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + DeleteSessionRequest.getDefaultInstance(), + SpannerGrpc.getDeleteSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + ListSessionsRequest.getDefaultInstance(), + SpannerGrpc.getListSessionsMethod())); + + assertNull( + configurator.configure( + inputCallContext, + BeginTransactionRequest.getDefaultInstance(), + SpannerGrpc.getBeginTransactionMethod())); + assertNull( + configurator.configure( + inputCallContext, CommitRequest.getDefaultInstance(), SpannerGrpc.getCommitMethod())); + assertNull( + configurator.configure( + inputCallContext, + RollbackRequest.getDefaultInstance(), + SpannerGrpc.getRollbackMethod())); + + assertNull( + configurator.configure( + inputCallContext, + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod())); + assertNull( + configurator.configure( + inputCallContext, + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteStreamingSqlMethod())); + assertNull( + configurator.configure( + inputCallContext, + ExecuteBatchDmlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteBatchDmlMethod())); + assertNull( + configurator.configure( + inputCallContext, ReadRequest.getDefaultInstance(), SpannerGrpc.getReadMethod())); + assertNull( + configurator.configure( + inputCallContext, + ReadRequest.getDefaultInstance(), + SpannerGrpc.getStreamingReadMethod())); + + assertNull( + configurator.configure( + inputCallContext, + PartitionQueryRequest.getDefaultInstance(), + SpannerGrpc.getPartitionQueryMethod())); + assertNull( + configurator.configure( + inputCallContext, + PartitionReadRequest.getDefaultInstance(), + SpannerGrpc.getPartitionReadMethod())); } @Test @@ -795,49 +798,42 @@ public void testSpannerCallContextTimeoutConfigurator_WithTimeouts() { ApiCallContext inputCallContext = GrpcCallContext.createDefault(); - assertThat( - configurator.configure( - inputCallContext, - BatchCreateSessionsRequest.getDefaultInstance(), - SpannerGrpc.getBatchCreateSessionsMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - CreateSessionRequest.getDefaultInstance(), - SpannerGrpc.getCreateSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - DeleteSessionRequest.getDefaultInstance(), - SpannerGrpc.getDeleteSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - GetSessionRequest.getDefaultInstance(), - SpannerGrpc.getGetSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - DeleteSessionRequest.getDefaultInstance(), - SpannerGrpc.getDeleteSessionMethod())) - .isNull(); - assertThat( - configurator.configure( - inputCallContext, - ListSessionsRequest.getDefaultInstance(), - SpannerGrpc.getListSessionsMethod())) - .isNull(); - - assertThat( - configurator.configure( - inputCallContext, - BeginTransactionRequest.getDefaultInstance(), - SpannerGrpc.getBeginTransactionMethod())) - .isNull(); + assertNull( + configurator.configure( + inputCallContext, + BatchCreateSessionsRequest.getDefaultInstance(), + SpannerGrpc.getBatchCreateSessionsMethod())); + assertNull( + configurator.configure( + inputCallContext, + CreateSessionRequest.getDefaultInstance(), + SpannerGrpc.getCreateSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + DeleteSessionRequest.getDefaultInstance(), + SpannerGrpc.getDeleteSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + GetSessionRequest.getDefaultInstance(), + SpannerGrpc.getGetSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + DeleteSessionRequest.getDefaultInstance(), + SpannerGrpc.getDeleteSessionMethod())); + assertNull( + configurator.configure( + inputCallContext, + ListSessionsRequest.getDefaultInstance(), + SpannerGrpc.getListSessionsMethod())); + + assertNull( + configurator.configure( + inputCallContext, + BeginTransactionRequest.getDefaultInstance(), + SpannerGrpc.getBeginTransactionMethod())); assertThat( configurator .configure( @@ -855,12 +851,11 @@ public void testSpannerCallContextTimeoutConfigurator_WithTimeouts() { .getTimeout()) .isEqualTo(Duration.ofSeconds(8L)); - assertThat( - configurator.configure( - inputCallContext, - ExecuteSqlRequest.getDefaultInstance(), - SpannerGrpc.getExecuteSqlMethod())) - .isNull(); + assertNull( + configurator.configure( + inputCallContext, + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod())); assertThat( configurator .configure( @@ -877,10 +872,9 @@ public void testSpannerCallContextTimeoutConfigurator_WithTimeouts() { SpannerGrpc.getExecuteBatchDmlMethod()) .getTimeout()) .isEqualTo(Duration.ofSeconds(1L)); - assertThat( - configurator.configure( - inputCallContext, ReadRequest.getDefaultInstance(), SpannerGrpc.getReadMethod())) - .isNull(); + assertNull( + configurator.configure( + inputCallContext, ReadRequest.getDefaultInstance(), SpannerGrpc.getReadMethod())); assertThat( configurator .configure( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java index 55df44a96df..f8462693fe6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -217,7 +218,8 @@ public void usesPreparedTransaction() { com.google.protobuf.Timestamp.newBuilder() .setSeconds(System.currentTimeMillis() * 1000)) .build())); - when(rpc.beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap())) + when(rpc.beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( invocation -> ApiFutures.immediateFuture( @@ -241,7 +243,8 @@ public void usesPreparedTransaction() { mgr.commit(); } verify(rpc, times(1)) - .beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap()); + .beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true)); } } @@ -278,7 +281,8 @@ public void inlineBegin() { com.google.protobuf.Timestamp.newBuilder() .setSeconds(System.currentTimeMillis() * 1000)) .build())); - when(rpc.beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap())) + when(rpc.beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( invocation -> ApiFutures.immediateFuture( @@ -286,7 +290,7 @@ public void inlineBegin() { .setId(ByteString.copyFromUtf8(UUID.randomUUID().toString())) .build())); final AtomicInteger transactionsStarted = new AtomicInteger(); - when(rpc.executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap())) + when(rpc.executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( invocation -> { ResultSet.Builder builder = @@ -332,9 +336,10 @@ public void inlineBegin() { } // BeginTransaction should not be called, as we are inlining it with the ExecuteSql request. verify(rpc, Mockito.never()) - .beginTransaction(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap()); + .beginTransaction(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true)); // We should have 2 ExecuteSql requests. - verify(rpc, times(2)).executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap()); + verify(rpc, times(2)) + .executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap(), eq(true)); // But only 1 with a BeginTransaction. assertThat(transactionsStarted.get()).isEqualTo(1); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java index 04ac46d887b..df8245e6acb 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -100,7 +101,7 @@ public void setUp() { MockitoAnnotations.initMocks(this); firstRun = true; when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); - when(rpc.executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap())) + when(rpc.executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( invocation -> { ResultSet.Builder builder = @@ -160,7 +161,8 @@ public void usesPreparedTransaction() { .setCreateTime( Timestamp.newBuilder().setSeconds(System.currentTimeMillis() * 1000)) .build())); - when(rpc.beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap())) + when(rpc.beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( invocation -> ApiFutures.immediateFuture( @@ -180,7 +182,8 @@ public void usesPreparedTransaction() { DatabaseClient client = spanner.getDatabaseClient(db); client.readWriteTransaction().run(transaction -> null); verify(rpc, times(1)) - .beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap()); + .beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true)); } } @@ -294,9 +297,10 @@ public void prepareReadWriteTransaction() { return null; }); verify(rpc, Mockito.never()) - .beginTransaction(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap()); + .beginTransaction(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true)); verify(rpc, Mockito.never()) - .beginTransactionAsync(Mockito.any(BeginTransactionRequest.class), Mockito.anyMap()); + .beginTransactionAsync( + Mockito.any(BeginTransactionRequest.class), Mockito.anyMap(), eq(true)); assertThat(usedInlinedBegin).isTrue(); } @@ -311,7 +315,7 @@ private long[] batchDmlException(int status) { .setRpc(rpc) .build(); when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(transaction); - when(session.beginTransactionAsync()) + when(session.beginTransactionAsync(true)) .thenReturn( ApiFutures.immediateFuture(ByteString.copyFromUtf8(UUID.randomUUID().toString()))); when(session.getName()).thenReturn(SessionId.of("p", "i", "d", "test").getName()); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java index 23ec9c682c8..d32dc518714 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java @@ -19,12 +19,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +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.Assume.assumeTrue; import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.ApiClientHeaderProvider; import com.google.api.gax.rpc.HeaderProvider; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.OAuth2Credentials; @@ -42,8 +47,10 @@ import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.TransactionRunner; import com.google.cloud.spanner.spi.v1.GapicSpannerRpc.AdminRequestsLimitExceededRetryAlgorithm; import com.google.cloud.spanner.spi.v1.SpannerRpc.Option; +import com.google.common.collect.ImmutableList; import com.google.protobuf.ListValue; import com.google.rpc.ErrorInfo; import com.google.spanner.v1.ExecuteSqlRequest; @@ -136,6 +143,7 @@ public class GapicSpannerRpcTest { private static Metadata lastSeenHeaders; private static String defaultUserAgent; private static Spanner spanner; + private static boolean isRouteToLeader; @Parameter public Dialect dialect; @@ -173,6 +181,17 @@ public ServerCall.Listener interceptCall( String auth = headers.get(Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)); assertThat(auth).isEqualTo("Bearer " + VARIABLE_OAUTH_TOKEN); + if (call.getMethodDescriptor() + .equals(SpannerGrpc.getExecuteStreamingSqlMethod()) + || call.getMethodDescriptor().equals(SpannerGrpc.getExecuteSqlMethod())) { + String routeToLeaderHeader = + headers.get( + Key.of( + "x-goog-spanner-route-to-leader", + Metadata.ASCII_STRING_MARSHALLER)); + isRouteToLeader = + (routeToLeaderHeader != null && routeToLeaderHeader.equals("true")); + } return Contexts.interceptCall(Context.current(), call, headers, next); } }) @@ -194,6 +213,7 @@ public void reset() throws InterruptedException { server.shutdown(); server.awaitTermination(); } + isRouteToLeader = false; } @Test @@ -207,15 +227,14 @@ public void testCallCredentialsProviderPreferenceAboveCredentials() { GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); // GoogleAuthLibraryCallCredentials doesn't implement equals, so we can only check for the // existence. - assertThat( - rpc.newCallContext( - optionsMap, - "/some/resource", - GetSessionRequest.getDefaultInstance(), - SpannerGrpc.getGetSessionMethod()) - .getCallOptions() - .getCredentials()) - .isNotNull(); + assertNotNull( + rpc.newCallContext( + optionsMap, + "/some/resource", + GetSessionRequest.getDefaultInstance(), + SpannerGrpc.getGetSessionMethod()) + .getCallOptions() + .getCredentials()); rpc.shutdown(); } @@ -228,15 +247,14 @@ public void testCallCredentialsProviderReturnsNull() { .setCallCredentialsProvider(() -> null) .build(); GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); - assertThat( - rpc.newCallContext( - optionsMap, - "/some/resource", - GetSessionRequest.getDefaultInstance(), - SpannerGrpc.getGetSessionMethod()) - .getCallOptions() - .getCredentials()) - .isNull(); + assertNull( + rpc.newCallContext( + optionsMap, + "/some/resource", + GetSessionRequest.getDefaultInstance(), + SpannerGrpc.getGetSessionMethod()) + .getCallOptions() + .getCredentials()); rpc.shutdown(); } @@ -248,15 +266,14 @@ public void testNoCallCredentials() { .setCredentials(STATIC_CREDENTIALS) .build(); GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); - assertThat( - rpc.newCallContext( - optionsMap, - "/some/resource", - GetSessionRequest.getDefaultInstance(), - SpannerGrpc.getGetSessionMethod()) - .getCallOptions() - .getCredentials()) - .isNull(); + assertNull( + rpc.newCallContext( + optionsMap, + "/some/resource", + GetSessionRequest.getDefaultInstance(), + SpannerGrpc.getGetSessionMethod()) + .getCallOptions() + .getCredentials()); rpc.shutdown(); } @@ -374,7 +391,66 @@ public ApiCallContext configure( public void testNewCallContextWithNullRequestAndNullMethod() { SpannerOptions options = SpannerOptions.newBuilder().setProjectId("some-project").build(); GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); - assertThat(rpc.newCallContext(optionsMap, "/some/resource", null, null)).isNotNull(); + assertNotNull(rpc.newCallContext(optionsMap, "/some/resource", null, null)); + rpc.shutdown(); + } + + @Test + public void testNewCallContextWithRouteToLeaderHeader() { + SpannerOptions options = + SpannerOptions.newBuilder().setProjectId("some-project").enableLeaderAwareRouting().build(); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); + GrpcCallContext callContext = + rpc.newCallContext( + optionsMap, + "/some/resource", + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod(), + true); + assertNotNull(callContext); + assertEquals( + ImmutableList.of("true"), + callContext.getExtraHeaders().get("x-goog-spanner-route-to-leader")); + assertEquals( + ImmutableList.of("projects/some-project"), + callContext.getExtraHeaders().get(ApiClientHeaderProvider.getDefaultResourceHeaderKey())); + rpc.shutdown(); + } + + @Test + public void testNewCallContextWithoutRouteToLeaderHeader() { + SpannerOptions options = + SpannerOptions.newBuilder().enableLeaderAwareRouting().setProjectId("some-project").build(); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); + GrpcCallContext callContext = + rpc.newCallContext( + optionsMap, + "/some/resource", + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod(), + false); + assertNotNull(callContext); + assertNull(callContext.getExtraHeaders().get("x-goog-spanner-route-to-leader")); + rpc.shutdown(); + } + + @Test + public void testNewCallContextWithRouteToLeaderHeaderAndLarDisabled() { + SpannerOptions options = + SpannerOptions.newBuilder() + .setProjectId("some-project") + .disableLeaderAwareRouting() + .build(); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); + GrpcCallContext callContext = + rpc.newCallContext( + optionsMap, + "/some/resource", + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod(), + true); + assertNotNull(callContext); + assertNull(callContext.getExtraHeaders().get("x-goog-spanner-route-to-leader")); rpc.shutdown(); } @@ -449,6 +525,56 @@ public void testCustomUserAgent() { } } + @Test + public void testRouteToLeaderHeaderForReadOnly() { + final SpannerOptions options = + createSpannerOptions().toBuilder().enableLeaderAwareRouting().build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertFalse(isRouteToLeader); + } + } + + @Test + public void testRouteToLeaderHeaderForReadWrite() { + final SpannerOptions options = + createSpannerOptions().toBuilder().enableLeaderAwareRouting().build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + TransactionRunner runner = databaseClient.readWriteTransaction(); + runner.run( + transaction -> { + transaction.executeUpdate(UPDATE_FOO_STATEMENT); + return null; + }); + } + assertTrue(isRouteToLeader); + } + + @Test + public void testRouteToLeaderHeaderWithLeaderAwareRoutingDisabled() { + final SpannerOptions options = + createSpannerOptions().toBuilder().disableLeaderAwareRouting().build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + TransactionRunner runner = databaseClient.readWriteTransaction(); + runner.run( + transaction -> { + transaction.executeUpdate(UPDATE_FOO_STATEMENT); + return null; + }); + } + assertFalse(isRouteToLeader); + } + private SpannerOptions createSpannerOptions() { String endpoint = address.getHostString() + ":" + server.getPort(); return SpannerOptions.newBuilder() diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java index 07e13626c18..cc43e2dc334 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java @@ -15,11 +15,12 @@ */ package com.google.cloud.spanner.spi.v1; -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import io.grpc.Metadata; import io.grpc.Metadata.Key; import java.util.List; @@ -77,9 +78,20 @@ public void testNewExtraHeaders() { SpannerMetadataProvider metadataProvider = SpannerMetadataProvider.create(ImmutableMap.of(), "header1"); Map> extraHeaders = metadataProvider.newExtraHeaders(null, "value1"); - assertThat(extraHeaders) - .containsExactlyEntriesIn( - ImmutableMap.>of("header1", ImmutableList.of("value1"))); + Map> expectedHeaders = + ImmutableMap.>of("header1", ImmutableList.of("value1")); + assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual()); + } + + @Test + public void testNewRouteToLeaderHeader() { + SpannerMetadataProvider metadataProvider = + SpannerMetadataProvider.create(ImmutableMap.of(), "header1"); + Map> extraHeaders = metadataProvider.newRouteToLeaderHeader(); + Map> expectedHeaders = + ImmutableMap.>of( + "x-goog-spanner-route-to-leader", ImmutableList.of("true")); + assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual()); } private String getResourceHeaderValue( From 1b56b94b6be4539df6dad72c9387efb9f544c32f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 15:04:00 +0530 Subject: [PATCH 15/15] chore(main): release 6.41.0 (#2397) 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 62847c62d83..49cc40d9ef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [6.41.0](https://github.com/googleapis/java-spanner/compare/v6.40.1...v6.41.0) (2023-04-28) + + +### Features + +* Add TransactionExecutionOptions support to executor. ([#2396](https://github.com/googleapis/java-spanner/issues/2396)) ([8327f21](https://github.com/googleapis/java-spanner/commit/8327f210df86bf681ffed6a78ccc9e8fd899c967)) +* Leader Aware Routing ([#2214](https://github.com/googleapis/java-spanner/issues/2214)) ([9695ace](https://github.com/googleapis/java-spanner/commit/9695acee9195b50e525d87700e86d701b1d9eed2)) +* Make leak detection configurable for connections ([#2405](https://github.com/googleapis/java-spanner/issues/2405)) ([85213c8](https://github.com/googleapis/java-spanner/commit/85213c8764fcb7fb12df49baaac9bd00e095f269)) + + +### Dependencies + +* Update dependency com.google.api.grpc:proto-google-cloud-spanner-executor-v1 to v1.4.0 ([#2395](https://github.com/googleapis/java-spanner/issues/2395)) ([02dc53c](https://github.com/googleapis/java-spanner/commit/02dc53c097bae3f20d7915fecc9c236c4a5f91f9)) +* Update dependency com.google.cloud:google-cloud-monitoring to v3.17.0 ([#2406](https://github.com/googleapis/java-spanner/issues/2406)) ([d46097f](https://github.com/googleapis/java-spanner/commit/d46097f9f17d9009d211c8c0f16b3e084f8fdbad)) +* Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.8.0 ([#2400](https://github.com/googleapis/java-spanner/issues/2400)) ([b815cb8](https://github.com/googleapis/java-spanner/commit/b815cb88ff29fb5b9a5d7998e765548244f287c1)) +* Update dependency com.google.cloud:google-cloud-trace to v2.16.0 ([#2407](https://github.com/googleapis/java-spanner/issues/2407)) ([7993be2](https://github.com/googleapis/java-spanner/commit/7993be25e9f380071cded2fa4c2bf630d760a53e)) +* Update dependency org.junit.vintage:junit-vintage-engine to v5.9.3 ([#2401](https://github.com/googleapis/java-spanner/issues/2401)) ([8aa7a1d](https://github.com/googleapis/java-spanner/commit/8aa7a1dbbf484446ae8eed3cb27d16fc65e6de83)) + ## [6.40.1](https://github.com/googleapis/java-spanner/compare/v6.40.0...v6.40.1) (2023-04-17) diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index c72af3981da..5bd8f5d7852 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.40.2-SNAPSHOT + 6.41.0 pom com.google.cloud @@ -53,48 +53,48 @@ com.google.cloud google-cloud-spanner - 6.40.2-SNAPSHOT + 6.41.0 com.google.cloud google-cloud-spanner-executor - 6.40.2-SNAPSHOT + 6.41.0 com.google.cloud google-cloud-spanner test-jar - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 62038c43c76..d13c5f2a726 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.40.2-SNAPSHOT + 6.41.0 jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 3156365549d..617d7cd48aa 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.40.2-SNAPSHOT + 6.41.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.40.2-SNAPSHOT + 6.41.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 1ad665c26fe..1b72f13e8cf 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.40.2-SNAPSHOT + 6.41.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.40.2-SNAPSHOT + 6.41.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index 417be82d055..50a96471291 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.40.2-SNAPSHOT + 6.41.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.40.2-SNAPSHOT + 6.41.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 0d78faf31db..3adcf0d831a 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.40.2-SNAPSHOT + 6.41.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/pom.xml b/pom.xml index 5832d98e1cc..140627e37f0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.40.2-SNAPSHOT + 6.41.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.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.40.2-SNAPSHOT + 6.41.0 com.google.cloud google-cloud-spanner - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index f5e38b90c1c..d7fa0946a8b 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.40.2-SNAPSHOT + 6.41.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.40.2-SNAPSHOT + 6.41.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 9c9c77d5364..d23136ba700 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.40.2-SNAPSHOT + 6.41.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.40.2-SNAPSHOT + 6.41.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index d2d1b2cbc87..a9c8352ed01 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.40.2-SNAPSHOT + 6.41.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 001f912ea11..865cd43ece9 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.40.2-SNAPSHOT + 6.41.0 diff --git a/versions.txt b/versions.txt index 6d66441bcf9..21d06554b43 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.40.1:6.40.2-SNAPSHOT -proto-google-cloud-spanner-v1:6.40.1:6.40.2-SNAPSHOT -proto-google-cloud-spanner-admin-database-v1:6.40.1:6.40.2-SNAPSHOT -grpc-google-cloud-spanner-v1:6.40.1:6.40.2-SNAPSHOT -grpc-google-cloud-spanner-admin-instance-v1:6.40.1:6.40.2-SNAPSHOT -grpc-google-cloud-spanner-admin-database-v1:6.40.1:6.40.2-SNAPSHOT -google-cloud-spanner:6.40.1:6.40.2-SNAPSHOT -google-cloud-spanner-executor:6.40.1:6.40.2-SNAPSHOT +proto-google-cloud-spanner-admin-instance-v1:6.41.0:6.41.0 +proto-google-cloud-spanner-v1:6.41.0:6.41.0 +proto-google-cloud-spanner-admin-database-v1:6.41.0:6.41.0 +grpc-google-cloud-spanner-v1:6.41.0:6.41.0 +grpc-google-cloud-spanner-admin-instance-v1:6.41.0:6.41.0 +grpc-google-cloud-spanner-admin-database-v1:6.41.0:6.41.0 +google-cloud-spanner:6.41.0:6.41.0 +google-cloud-spanner-executor:6.41.0:6.41.0