diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml
index 18d92e5a28..0cda6b04f7 100644
--- a/.github/workflows/auto-release.yaml
+++ b/.github/workflows/auto-release.yaml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
if: contains(github.head_ref, 'release-please')
steps:
- - uses: actions/github-script@v7
+ - uses: actions/github-script@v8
with:
github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
debug: true
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8717db17ae..e2e6881c78 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- java: [11, 17, 21]
+ java: [11, 17, 21, 25]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml
index 6d1265ec78..0e85f80524 100644
--- a/.github/workflows/hermetic_library_generation.yaml
+++ b/.github/workflows/hermetic_library_generation.yaml
@@ -32,12 +32,12 @@ jobs:
else
echo "SHOULD_RUN=true" >> $GITHUB_ENV
fi
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
if: env.SHOULD_RUN == 'true'
with:
fetch-depth: 0
token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }}
- - uses: googleapis/sdk-platform-java/.github/scripts@v2.62.2
+ - uses: googleapis/sdk-platform-java/.github/scripts@v2.62.3
if: env.SHOULD_RUN == 'true'
with:
base_ref: ${{ github.base_ref }}
diff --git a/.github/workflows/integration-tests-against-emulator-with-regular-session.yaml b/.github/workflows/integration-tests-against-emulator-with-regular-session.yaml
index 06e7e416d9..371620cf5a 100644
--- a/.github/workflows/integration-tests-against-emulator-with-regular-session.yaml
+++ b/.github/workflows/integration-tests-against-emulator-with-regular-session.yaml
@@ -16,18 +16,18 @@ jobs:
- 9020:9020
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: stCarolas/setup-maven@v5
with:
maven-version: 3.8.1
# Build with JDK 11 and run tests with JDK 8
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 11
distribution: temurin
- name: Compiling main library
run: .kokoro/build.sh
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 8
distribution: temurin
diff --git a/.github/workflows/integration-tests-against-emulator.yaml b/.github/workflows/integration-tests-against-emulator.yaml
index f4ac97a8fe..cd677dd1a6 100644
--- a/.github/workflows/integration-tests-against-emulator.yaml
+++ b/.github/workflows/integration-tests-against-emulator.yaml
@@ -16,18 +16,18 @@ jobs:
- 9020:9020
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: stCarolas/setup-maven@v5
with:
maven-version: 3.8.1
# Build with JDK 11 and run tests with JDK 8
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 11
distribution: temurin
- name: Compiling main library
run: .kokoro/build.sh
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 8
distribution: temurin
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index e646d4e2c2..6040b5ceee 100644
--- a/.github/workflows/unmanaged_dependency_check.yaml
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -5,8 +5,8 @@ jobs:
unmanaged_dependency_check:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@v5
+ - uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 11
@@ -17,6 +17,6 @@ jobs:
# repository
.kokoro/build.sh
- name: Unmanaged dependency check
- uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.52.2
+ uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.52.3
with:
bom-path: google-cloud-spanner-bom/pom.xml
diff --git a/.github/workflows/update_generation_config.yaml b/.github/workflows/update_generation_config.yaml
index a7e14bb483..59e39834dd 100644
--- a/.github/workflows/update_generation_config.yaml
+++ b/.github/workflows/update_generation_config.yaml
@@ -26,7 +26,7 @@ jobs:
# the branch into which the pull request is merged
base_branch: main
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }}
diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg
index 5554627daa..b7567eeb7d 100644
--- a/.kokoro/presubmit/graalvm-native-a.cfg
+++ b/.kokoro/presubmit/graalvm-native-a.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.52.2" # {x-version-update:google-cloud-shared-dependencies:current}
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current}
}
env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg
index 1089437409..c7205f0abd 100644
--- a/.kokoro/presubmit/graalvm-native-b.cfg
+++ b/.kokoro/presubmit/graalvm-native-b.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.52.2" # {x-version-update:google-cloud-shared-dependencies:current}
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current}
}
env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native-c.cfg b/.kokoro/presubmit/graalvm-native-c.cfg
index 5465e51923..f6ab8976a5 100644
--- a/.kokoro/presubmit/graalvm-native-c.cfg
+++ b/.kokoro/presubmit/graalvm-native-c.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.52.2" # {x-version-update:google-cloud-shared-dependencies:current}
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current}
}
env_vars: {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56dd8b0ae4..7fd2983cfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,37 @@
# Changelog
+## [6.102.0](https://github.com/googleapis/java-spanner/compare/v6.101.1...v6.102.0) (2025-10-08)
+
+
+### Features
+
+* Add connection property for gRPC interceptor provider ([#4149](https://github.com/googleapis/java-spanner/issues/4149)) ([deb8dff](https://github.com/googleapis/java-spanner/commit/deb8dff6c01c37a3158e8f4a28ef5e821d10092a))
+* Support statement_timeout in connection url ([#4103](https://github.com/googleapis/java-spanner/issues/4103)) ([542c6aa](https://github.com/googleapis/java-spanner/commit/542c6aa63bfdd526070f14cb76921dd34527c1f9))
+
+
+### Bug Fixes
+
+* Automatically set default_sequence_kind for CREATE SEQUENCE ([#4105](https://github.com/googleapis/java-spanner/issues/4105)) ([3beea6a](https://github.com/googleapis/java-spanner/commit/3beea6ac4eb53b70db34e0a2d2e33e56f450c88b))
+* **deps:** Update the Java code generator (gapic-generator-java) to 2.62.3 ([7047a3a](https://github.com/googleapis/java-spanner/commit/7047a3ae31aae51e9e23758fe004b93855a0ee4b))
+
+
+### Dependencies
+
+* Update actions/checkout action to v5 ([#4069](https://github.com/googleapis/java-spanner/issues/4069)) ([4c88eb9](https://github.com/googleapis/java-spanner/commit/4c88eb91a321aa718f957296012f9e7501c7caec))
+* Update actions/checkout action to v5 ([#4106](https://github.com/googleapis/java-spanner/issues/4106)) ([14ebdb3](https://github.com/googleapis/java-spanner/commit/14ebdb35c33442c4e0f70d63dce3425edb730525))
+* Update actions/setup-java action to v5 ([#4071](https://github.com/googleapis/java-spanner/issues/4071)) ([e23134a](https://github.com/googleapis/java-spanner/commit/e23134a2f864e8abd2890ac3a81ff6b668afbe63))
+* Update all dependencies ([#4099](https://github.com/googleapis/java-spanner/issues/4099)) ([b262edc](https://github.com/googleapis/java-spanner/commit/b262edcfc4713bb64986bc4acd3f02b69d3367f8))
+* Update dependency com.google.api.grpc:grpc-google-cloud-monitoring-v3 to v3.77.0 ([#4117](https://github.com/googleapis/java-spanner/issues/4117)) ([2451ca2](https://github.com/googleapis/java-spanner/commit/2451ca2abe1dd2de3907b88e8d18beab1a15a634))
+* Update dependency com.google.api.grpc:proto-google-cloud-monitoring-v3 to v3.77.0 ([#4143](https://github.com/googleapis/java-spanner/issues/4143)) ([6c9dc26](https://github.com/googleapis/java-spanner/commit/6c9dc26330cf66f196adc2203323a482e08f0325))
+* Update dependency com.google.api.grpc:proto-google-cloud-trace-v1 to v2.76.0 ([#4144](https://github.com/googleapis/java-spanner/issues/4144)) ([d566a42](https://github.com/googleapis/java-spanner/commit/d566a4295be018070169ba082a018394a2e60b45))
+* Update dependency com.google.cloud:google-cloud-monitoring to v3.77.0 ([#4145](https://github.com/googleapis/java-spanner/issues/4145)) ([8917c05](https://github.com/googleapis/java-spanner/commit/8917c054410e4035d6d4e201e43599d5ddc1fadd))
+* Update dependency com.google.cloud:google-cloud-monitoring to v3.77.0 ([#4146](https://github.com/googleapis/java-spanner/issues/4146)) ([4ebea1a](https://github.com/googleapis/java-spanner/commit/4ebea1adf726069084087ce46900f3174658055c))
+* Update dependency com.google.cloud:google-cloud-trace to v2.76.0 ([#4147](https://github.com/googleapis/java-spanner/issues/4147)) ([4b1d4af](https://github.com/googleapis/java-spanner/commit/4b1d4af19336e493af38a1e58c95786da3892d34))
+* Update dependency com.google.cloud:google-cloud-trace to v2.76.0 ([#4148](https://github.com/googleapis/java-spanner/issues/4148)) ([8f91a89](https://github.com/googleapis/java-spanner/commit/8f91a894771653213b6fcded5795349ad7ea6724))
+* Update dependency com.google.cloud:sdk-platform-java-config to v3.52.3 ([#4107](https://github.com/googleapis/java-spanner/issues/4107)) ([8a8a042](https://github.com/googleapis/java-spanner/commit/8a8a042494b092b3dddd0c9606a63197d8a23555))
+* Update dependency org.json:json to v20250517 ([#3881](https://github.com/googleapis/java-spanner/issues/3881)) ([5658c83](https://github.com/googleapis/java-spanner/commit/5658c8378aa2e8028d4ef7dfaf94b647f33cd812))
+* Update googleapis/sdk-platform-java action to v2.62.3 ([#4108](https://github.com/googleapis/java-spanner/issues/4108)) ([65913ec](https://github.com/googleapis/java-spanner/commit/65913ec0638fec4ea536cf42f8fe25460133f68e))
+
## [6.101.1](https://github.com/googleapis/java-spanner/compare/v6.101.0...v6.101.1) (2025-09-26)
diff --git a/README.md b/README.md
index 73f923590a..6fc3af24e7 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies:
com.google.cloud
google-cloud-spanner
- 6.99.0
+ 6.101.1
```
@@ -49,20 +49,20 @@ If you are using Maven without the BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:
```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.68.0')
+implementation platform('com.google.cloud:libraries-bom:26.69.0')
implementation 'com.google.cloud:google-cloud-spanner'
```
If you are using Gradle without BOM, add this to your dependencies:
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner:6.101.0'
+implementation 'com.google.cloud:google-cloud-spanner:6.101.1'
```
If you are using SBT, add this to your dependencies:
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.101.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.101.1"
```
## Authentication
@@ -731,7 +731,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.101.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.101.1
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index 47598f1c64..f976c0b4dd 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -24,7 +24,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
@@ -34,7 +34,7 @@
UTF-8
UTF-8
2.10.1
- 1.47.0
+ 1.54.1
@@ -49,17 +49,17 @@
com.google.cloud.opentelemetry
exporter-trace
- 0.33.0
+ 0.36.0
com.google.cloud.opentelemetry
exporter-metrics
- 0.33.0
+ 0.36.0
com.google.cloud
google-cloud-monitoring
- 3.63.0
+ 3.77.0
@@ -87,17 +87,10 @@
re2j
1.8
-
- io.opentelemetry
- opentelemetry-bom
- 1.50.0
- pom
- import
-
com.google.cloud
google-cloud-spanner
- 6.99.0
+ 6.101.1
com.google.auto.value
@@ -118,7 +111,7 @@
commons-cli
commons-cli
- 1.9.0
+ 1.10.0
@@ -133,7 +126,7 @@
org.codehaus.mojo
exec-maven-plugin
- 3.5.0
+ 3.6.1
com.google.cloud.spanner.benchmark.LatencyBenchmark
false
@@ -142,7 +135,7 @@
com.spotify.fmt
fmt-maven-plugin
- 2.27
+ 2.29
diff --git a/generation_config.yaml b/generation_config.yaml
index f1a9a7abab..bdd59f3570 100644
--- a/generation_config.yaml
+++ b/generation_config.yaml
@@ -1,6 +1,6 @@
-gapic_generator_version: 2.62.2
-googleapis_commitish: 329ace5e3712a2e37d6159d4dcd998d8c73f261e
-libraries_bom_version: 26.68.0
+gapic_generator_version: 2.62.3
+googleapis_commitish: d9a16f2feec3d0f03899e48007a02ce154fc919d
+libraries_bom_version: 26.69.0
libraries:
- api_shortname: spanner
name_pretty: Cloud Spanner
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index 33e4677274..92de4f39b0 100644
--- a/google-cloud-spanner-bom/pom.xml
+++ b/google-cloud-spanner-bom/pom.xml
@@ -3,12 +3,12 @@
4.0.0
com.google.cloud
google-cloud-spanner-bom
- 6.101.1
+ 6.102.0
pom
com.google.cloud
sdk-platform-java-config
- 3.52.2
+ 3.52.3
Google Cloud Spanner BOM
@@ -53,43 +53,43 @@
com.google.cloud
google-cloud-spanner
- 6.101.1
+ 6.102.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.101.1
+ 6.102.0
@@ -100,7 +100,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.14.0
+ 3.14.1
1.8
1.8
diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml
index edabbdac62..c9b0170a06 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.101.1
+ 6.102.0
jar
Google Cloud Spanner Executor
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
@@ -64,7 +64,7 @@
com.google.cloud
google-cloud-trace
- 2.62.0
+ 2.76.0
io.grpc
@@ -137,7 +137,7 @@
com.google.api.grpc
proto-google-cloud-trace-v1
- 2.62.0
+ 2.76.0
com.google.api.grpc
@@ -170,12 +170,12 @@
commons-cli
commons-cli
- 1.9.0
+ 1.10.0
commons-io
commons-io
- 2.19.0
+ 2.20.0
@@ -202,7 +202,7 @@
org.apache.maven.surefire
surefire-junit4
- 3.5.3
+ 3.5.4
test
@@ -267,7 +267,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.5.3
+ 3.5.4
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 9bb8709315..67f9a08602 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.101.1
+ 6.102.0
jar
Google Cloud Spanner
https://github.com/googleapis/java-spanner
@@ -11,7 +11,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
google-cloud-spanner
@@ -269,12 +269,18 @@
com.google.cloud
google-cloud-monitoring
- 3.63.0
+ 3.77.0
com.google.api.grpc
proto-google-cloud-monitoring-v3
- 3.63.0
+ 3.77.0
+
+
+ com.google.api.grpc
+ grpc-google-cloud-monitoring-v3
+ 3.77.0
+ test
com.google.auth
@@ -428,7 +434,7 @@
org.json
json
- 20250107
+ 20250517
test
@@ -475,13 +481,13 @@
com.google.cloud
google-cloud-trace
- 2.62.0
+ 2.76.0
test
com.google.api.grpc
proto-google-cloud-trace-v1
- 2.62.0
+ 2.76.0
test
@@ -522,7 +528,11 @@
-classpath
org.openjdk.jmh.Main
- ${benchmark.name}
+ ${benchmark.name}
+ -rf
+ JSON
+ -rff
+ jmh-results.json
@@ -544,6 +554,21 @@
+
+ validate-benchmark
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ com.google.cloud.spanner.benchmarking.BenchmarkValidator
+ test
+
+
+
+
+
slow-tests
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MissingDefaultSequenceKindException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MissingDefaultSequenceKindException.java
index f153cf6fe4..80e0ac7efa 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MissingDefaultSequenceKindException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MissingDefaultSequenceKindException.java
@@ -29,8 +29,8 @@ public class MissingDefaultSequenceKindException extends SpannerException {
private static final Pattern PATTERN =
Pattern.compile(
- "The sequence kind of an identity column .+ is not specified\\. Please specify the"
- + " sequence kind explicitly or set the database option `default_sequence_kind`\\.");
+ ".*Please specify the sequence kind explicitly or set the database option"
+ + " `default_sequence_kind`\\.");
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
MissingDefaultSequenceKindException(
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java
index 40202a0eef..bedf660007 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java
@@ -22,8 +22,10 @@
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.core.NoCredentialsProvider;
+import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.rpc.PermissionDeniedException;
import com.google.auth.Credentials;
+import com.google.cloud.NoCredentials;
import com.google.cloud.monitoring.v3.MetricServiceClient;
import com.google.cloud.monitoring.v3.MetricServiceSettings;
import com.google.common.annotations.VisibleForTesting;
@@ -34,6 +36,7 @@
import com.google.monitoring.v3.ProjectName;
import com.google.monitoring.v3.TimeSeries;
import com.google.protobuf.Empty;
+import io.grpc.ManagedChannelBuilder;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
@@ -79,7 +82,7 @@ static SpannerCloudMonitoringExporter create(
throws IOException {
MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder();
CredentialsProvider credentialsProvider;
- if (credentials == null) {
+ if (credentials == null || credentials instanceof NoCredentials) {
credentialsProvider = NoCredentialsProvider.create();
} else {
credentialsProvider = FixedCredentialsProvider.create(credentials);
@@ -92,6 +95,19 @@ static SpannerCloudMonitoringExporter create(
settingsBuilder.setUniverseDomain(universeDomain);
}
+ if (System.getProperty("jmh.monitoring-server-port") != null) {
+ settingsBuilder.setTransportChannelProvider(
+ InstantiatingGrpcChannelProvider.newBuilder()
+ .setCredentials(NoCredentials.getInstance())
+ .setChannelConfigurator(
+ managedChannelBuilder ->
+ ManagedChannelBuilder.forAddress(
+ "0.0.0.0",
+ Integer.parseInt(System.getProperty("jmh.monitoring-server-port")))
+ .usePlaintext())
+ .build());
+ }
+
Duration timeout = Duration.ofMinutes(1);
// TODO: createServiceTimeSeries needs special handling if the request failed. Leaving
// it as not retried for now.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
index 8f5baca64f..34fad2a69c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
@@ -149,12 +149,56 @@ static final class ClosedException extends RuntimeException {
this.dbAdminClient = new DatabaseAdminClientImpl(options.getProjectId(), gapicRpc);
this.instanceClient =
new InstanceAdminClientImpl(options.getProjectId(), gapicRpc, dbAdminClient);
+ logSpannerOptions(options);
}
SpannerImpl(SpannerOptions options) {
this(options.getSpannerRpcV1(), options);
}
+ private void logSpannerOptions(SpannerOptions options) {
+ logger.log(
+ Level.INFO,
+ "Spanner options: "
+ + "\nProject ID: "
+ + options.getProjectId()
+ + "\nHost: "
+ + options.getHost()
+ + "\nNum gRPC channels: "
+ + options.getNumChannels()
+ + "\nLeader aware routing enabled: "
+ + options.isLeaderAwareRoutingEnabled()
+ + "\nDirect access enabled: "
+ + options.isEnableDirectAccess()
+ + "\nActive Tracing Framework: "
+ + SpannerOptions.getActiveTracingFramework()
+ + "\nAPI tracing enabled: "
+ + options.isEnableApiTracing()
+ + "\nExtended tracing enabled: "
+ + options.isEnableExtendedTracing()
+ + "\nEnd to end tracing enabled: "
+ + options.isEndToEndTracingEnabled()
+ + "\nBuilt-in metrics enabled: "
+ + options.isEnableBuiltInMetrics());
+ if (options.getSessionPoolOptions() != null) {
+ logger.log(
+ Level.INFO,
+ "Session pool options: "
+ + "\nSession pool min sessions: "
+ + options.getSessionPoolOptions().getMinSessions()
+ + "\nSession pool max sessions: "
+ + options.getSessionPoolOptions().getMaxSessions()
+ + "\nMultiplexed sessions enabled: "
+ + options.getSessionPoolOptions().getUseMultiplexedSession()
+ + "\nMultiplexed sessions enabled for RW: "
+ + options.getSessionPoolOptions().getUseMultiplexedSessionForRW()
+ + "\nMultiplexed sessions enabled for blind write: "
+ + options.getSessionPoolOptions().getUseMultiplexedSessionBlindWrite()
+ + "\nMultiplexed sessions enabled for partitioned ops: "
+ + options.getSessionPoolOptions().getUseMultiplexedSessionPartitionedOps());
+ }
+ }
+
/** Returns the {@link SpannerRpc} of this {@link SpannerImpl} instance. */
SpannerRpc getRpc() {
return gapicRpc;
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 af4ed58bad..765114dc68 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
@@ -2013,6 +2013,11 @@ public CallCredentialsProvider getCallCredentialsProvider() {
}
private boolean usesNoCredentials() {
+ // When JMH is enabled, we need to enable built-in metrics
+ if (System.getProperty("jmh.enabled") != null
+ && System.getProperty("jmh.enabled").equals("true")) {
+ return false;
+ }
return Objects.equals(getCredentials(), NoCredentials.getInstance());
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
index 57916aae58..3d796af4f0 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
@@ -20,6 +20,7 @@
import static com.google.cloud.spanner.connection.ReadOnlyStalenessUtil.toChronoUnit;
import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Options.RpcPriority;
@@ -827,6 +828,54 @@ public CredentialsProvider convert(String credentialsProviderName) {
}
}
+ static class GrpcInterceptorProviderConverter
+ implements ClientSideStatementValueConverter {
+ static final GrpcInterceptorProviderConverter INSTANCE = new GrpcInterceptorProviderConverter();
+
+ private GrpcInterceptorProviderConverter() {}
+
+ @Override
+ public Class getParameterClass() {
+ return GrpcInterceptorProvider.class;
+ }
+
+ @Override
+ public GrpcInterceptorProvider convert(String interceptorProviderName) {
+ if (!Strings.isNullOrEmpty(interceptorProviderName)) {
+ try {
+ Class extends GrpcInterceptorProvider> clazz =
+ (Class extends GrpcInterceptorProvider>) Class.forName(interceptorProviderName);
+ Constructor extends GrpcInterceptorProvider> constructor =
+ clazz.getDeclaredConstructor();
+ return constructor.newInstance();
+ } catch (ClassNotFoundException classNotFoundException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Unknown or invalid GrpcInterceptorProvider class name: " + interceptorProviderName,
+ classNotFoundException);
+ } catch (NoSuchMethodException noSuchMethodException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "GrpcInterceptorProvider "
+ + interceptorProviderName
+ + " does not have a public no-arg constructor.",
+ noSuchMethodException);
+ } catch (InvocationTargetException
+ | InstantiationException
+ | IllegalAccessException exception) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Failed to create an instance of "
+ + interceptorProviderName
+ + ": "
+ + exception.getMessage(),
+ exception);
+ }
+ }
+ return null;
+ }
+ }
+
/** Converter for converting strings to {@link Dialect} values. */
static class DialectConverter implements ClientSideStatementValueConverter {
static final DialectConverter INSTANCE = new DialectConverter();
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 8ae4c0fc5f..07ded59d85 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
@@ -44,6 +44,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperties.RETURN_COMMIT_STATS;
import static com.google.cloud.spanner.connection.ConnectionProperties.RPC_PRIORITY;
import static com.google.cloud.spanner.connection.ConnectionProperties.SAVEPOINT_SUPPORT;
+import static com.google.cloud.spanner.connection.ConnectionProperties.STATEMENT_TIMEOUT;
import static com.google.cloud.spanner.connection.ConnectionProperties.TRACING_PREFIX;
import static com.google.cloud.spanner.connection.ConnectionProperties.TRANSACTION_TIMEOUT;
@@ -345,7 +346,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
&& getDialect() == Dialect.POSTGRESQL
? Type.TRANSACTIONAL
: Type.NON_TRANSACTIONAL));
-
+ setInitialStatementTimeout(options.getInitialConnectionPropertyValue(STATEMENT_TIMEOUT));
// (Re)set the state of the connection to the default.
setDefaultTransactionOptions(getDefaultIsolationLevel());
}
@@ -379,6 +380,7 @@ && getDialect() == Dialect.POSTGRESQL
new ConnectionState(
options.getInitialConnectionPropertyValues(),
Suppliers.ofInstance(Type.NON_TRANSACTIONAL));
+ setInitialStatementTimeout(options.getInitialConnectionPropertyValue(STATEMENT_TIMEOUT));
setReadOnly(options.isReadOnly());
setAutocommit(options.isAutocommit());
setReturnCommitStats(options.isReturnCommitStats());
@@ -390,6 +392,21 @@ public Spanner getSpanner() {
return this.spanner;
}
+ private void setInitialStatementTimeout(Duration duration) {
+ if (duration == null || duration.isZero()) {
+ return;
+ }
+ com.google.protobuf.Duration protoDuration =
+ com.google.protobuf.Duration.newBuilder()
+ .setSeconds(duration.getSeconds())
+ .setNanos(duration.getNano())
+ .build();
+ TimeUnit unit =
+ ReadOnlyStalenessUtil.getAppropriateTimeUnit(
+ new ReadOnlyStalenessUtil.DurationGetter(protoDuration));
+ setStatementTimeout(ReadOnlyStalenessUtil.durationToUnits(protoDuration, unit), unit);
+ }
+
private DdlClient createDdlClient() {
return DdlClient.newBuilder()
.setDatabaseAdminClient(spanner.getDatabaseAdminClient())
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 b12cd21fa7..741989ff5b 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
@@ -33,6 +33,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT;
+import static com.google.cloud.spanner.connection.ConnectionProperties.GRPC_INTERCEPTOR_PROVIDER;
import static com.google.cloud.spanner.connection.ConnectionProperties.IS_EXPERIMENTAL_HOST;
import static com.google.cloud.spanner.connection.ConnectionProperties.LENIENT;
import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_COMMIT_DELAY;
@@ -59,6 +60,7 @@
import com.google.api.core.InternalApi;
import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.AccessToken;
@@ -75,6 +77,7 @@
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.GrpcInterceptorProviderConverter;
import com.google.cloud.spanner.connection.StatementExecutor.StatementExecutorType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
@@ -256,6 +259,9 @@ public class ConnectionOptions {
public static final String ENABLE_CHANNEL_PROVIDER_SYSTEM_PROPERTY = "ENABLE_CHANNEL_PROVIDER";
+ public static final String ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY =
+ "ENABLE_GRPC_INTERCEPTOR_PROVIDER";
+
/** Custom user agent string is only for other Google libraries. */
static final String USER_AGENT_PROPERTY_NAME = "userAgent";
@@ -656,19 +662,6 @@ private ConnectionOptions(Builder builder) {
// Create the initial connection state from the parsed properties in the connection URL.
this.initialConnectionState = new ConnectionState(connectionPropertyValues);
- // Check that at most one of credentials location, encoded credentials, credentials provider and
- // OUAuth token has been specified in the connection URI.
- Preconditions.checkArgument(
- Stream.of(
- getInitialConnectionPropertyValue(CREDENTIALS_URL),
- getInitialConnectionPropertyValue(ENCODED_CREDENTIALS),
- getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER),
- getInitialConnectionPropertyValue(OAUTH_TOKEN))
- .filter(Objects::nonNull)
- .count()
- <= 1,
- "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth"
- + " token");
checkGuardedProperty(
getInitialConnectionPropertyValue(ENCODED_CREDENTIALS),
ENABLE_ENCODED_CREDENTIALS_SYSTEM_PROPERTY,
@@ -683,6 +676,23 @@ private ConnectionOptions(Builder builder) {
getInitialConnectionPropertyValue(CHANNEL_PROVIDER),
ENABLE_CHANNEL_PROVIDER_SYSTEM_PROPERTY,
CHANNEL_PROVIDER_PROPERTY_NAME);
+ checkGuardedProperty(
+ getInitialConnectionPropertyValue(GRPC_INTERCEPTOR_PROVIDER),
+ ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY,
+ GRPC_INTERCEPTOR_PROVIDER.getName());
+ // Check that at most one of credentials location, encoded credentials, credentials provider and
+ // OUAuth token has been specified in the connection URI.
+ Preconditions.checkArgument(
+ Stream.of(
+ getInitialConnectionPropertyValue(CREDENTIALS_URL),
+ getInitialConnectionPropertyValue(ENCODED_CREDENTIALS),
+ getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER),
+ getInitialConnectionPropertyValue(OAUTH_TOKEN))
+ .filter(Objects::nonNull)
+ .count()
+ <= 1,
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth"
+ + " token");
boolean usePlainText =
getInitialConnectionPropertyValue(AUTO_CONFIG_EMULATOR)
@@ -999,6 +1009,19 @@ public TransportChannelProvider getChannelProvider() {
}
}
+ String getGrpcInterceptorProviderName() {
+ return getInitialConnectionPropertyValue(GRPC_INTERCEPTOR_PROVIDER);
+ }
+
+ /** Returns the gRPC interceptor provider that has been configured. */
+ public GrpcInterceptorProvider getGrpcInterceptorProvider() {
+ String interceptorProvider = getInitialConnectionPropertyValue(GRPC_INTERCEPTOR_PROVIDER);
+ if (interceptorProvider == null) {
+ return null;
+ }
+ return GrpcInterceptorProviderConverter.INSTANCE.convert(interceptorProvider);
+ }
+
/**
* The database role that is used for this connection. Assigning a role to a connection can be
* used to for example restrict the access of a connection to a specific set of tables.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java
index 3bb5d71e48..200de9bec8 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java
@@ -75,6 +75,7 @@
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_API_TRACING_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_END_TO_END_TRACING_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME;
+import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.IS_EXPERIMENTAL_HOST_PROPERTY_NAME;
@@ -101,6 +102,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperty.castProperty;
import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.DmlBatchUpdateCountVerificationFailedException;
import com.google.cloud.spanner.Options.RpcPriority;
@@ -286,6 +288,23 @@ public class ConnectionProperties {
null,
CredentialsProviderConverter.INSTANCE,
Context.STARTUP);
+ static final ConnectionProperty GRPC_INTERCEPTOR_PROVIDER =
+ create(
+ "grpc_interceptor_provider",
+ "The class name of a "
+ + GrpcInterceptorProvider.class.getName()
+ + " implementation that should be used to provide interceptors for the underlying"
+ + " Spanner client. This is a guarded property that can only be set if the Java"
+ + " System Property "
+ + ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY
+ + " has been set to true. This property should only be set to true on systems where"
+ + " an untrusted user cannot modify the connection URL, as using this property will"
+ + " dynamically invoke the constructor of the class specified. This means that any"
+ + " user that can modify the connection URL, can also dynamically invoke code on the"
+ + " host where the application is running.",
+ null,
+ StringValueConverter.INSTANCE,
+ Context.STARTUP);
static final ConnectionProperty USER_AGENT =
create(
@@ -494,6 +513,15 @@ public class ConnectionProperties {
.toArray(new ReadLockMode[0]),
ReadLockModeConverter.INSTANCE,
Context.USER);
+ static final ConnectionProperty STATEMENT_TIMEOUT =
+ create(
+ "statement_timeout",
+ "Adds a timeout to all statements executed on this connection. "
+ + "This property is only used when a statement timeout is specified.",
+ null,
+ null,
+ DurationConverter.INSTANCE,
+ Context.USER);
static final ConnectionProperty TRANSACTION_TIMEOUT =
create(
"transaction_timeout",
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
index c1cf3ae679..e78a646f07 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
@@ -166,6 +166,7 @@ static class SpannerPoolKey {
private final boolean isExperimentalHost;
private final Boolean enableDirectAccess;
private final String universeDomain;
+ private final String grpcInterceptorProvider;
@VisibleForTesting
static SpannerPoolKey of(ConnectionOptions options) {
@@ -202,6 +203,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException {
this.isExperimentalHost = options.isExperimentalHost();
this.enableDirectAccess = options.isEnableDirectAccess();
this.universeDomain = options.getUniverseDomain();
+ this.grpcInterceptorProvider = options.getGrpcInterceptorProviderName();
}
@Override
@@ -229,7 +231,8 @@ public boolean equals(Object o) {
&& Objects.equals(this.clientCertificateKey, other.clientCertificateKey)
&& Objects.equals(this.isExperimentalHost, other.isExperimentalHost)
&& Objects.equals(this.enableDirectAccess, other.enableDirectAccess)
- && Objects.equals(this.universeDomain, other.universeDomain);
+ && Objects.equals(this.universeDomain, other.universeDomain)
+ && Objects.equals(this.grpcInterceptorProvider, other.grpcInterceptorProvider);
}
@Override
@@ -253,7 +256,8 @@ public int hashCode() {
this.clientCertificateKey,
this.isExperimentalHost,
this.enableDirectAccess,
- this.universeDomain);
+ this.universeDomain,
+ this.grpcInterceptorProvider);
}
}
@@ -426,6 +430,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
if (key.universeDomain != null) {
builder.setUniverseDomain(key.universeDomain);
}
+ if (key.grpcInterceptorProvider != null) {
+ builder.setInterceptorProvider(options.getGrpcInterceptorProvider());
+ }
if (options.getConfigurator() != null) {
options.getConfigurator().configure(builder);
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/BenchmarkValidator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/BenchmarkValidator.java
new file mode 100644
index 0000000000..225197af6c
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/BenchmarkValidator.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.benchmarking;
+
+import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult;
+import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult.Percentile;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class BenchmarkValidator {
+
+ private final BaselineResult expectedResults;
+ private final List actualResults;
+
+ public BenchmarkValidator(String baselineFile, String actualFile) {
+ Gson gson = new Gson();
+ // Load expected result JSON from resource folder
+ this.expectedResults = gson.fromJson(loadJsonFromResources(baselineFile), BaselineResult.class);
+ // Load the actual result from current benchmarking run
+ this.actualResults =
+ gson.fromJson(
+ loadJsonFromFile(actualFile),
+ new TypeToken>() {}.getType());
+ }
+
+ void validate() {
+ // Validating the resultant percentile against expected percentile with allowed threshold
+ for (ActualBenchmarkResult actualResult : actualResults) {
+ BenchmarkResult expectResult = expectedResults.benchmarkResultMap.get(actualResult.benchmark);
+ if (expectResult == null) {
+ throw new ValidationException(
+ "Missing expected benchmark configuration for actual benchmarking");
+ }
+ Map actualPercentilesMap = actualResult.primaryMetric.scorePercentiles;
+ // We will only be comparing the percentiles(p50, p90, p90) which are configured in the
+ // expected percentiles. This allows some checks to be disabled if required.
+ for (Percentile expectedPercentile : expectResult.scorePercentiles) {
+ String percentile = expectedPercentile.percentile;
+ double difference =
+ calculatePercentageDifference(
+ expectedPercentile.baseline, actualPercentilesMap.get(percentile));
+ // if an absolute different in percentage is greater than allowed difference
+ // Then we are throwing validation error
+ if (Math.abs(Math.ceil(difference)) > expectedPercentile.difference) {
+ throw new ValidationException(
+ String.format(
+ "[%s][%s] Expected percentile %s[+/-%s] but got %s",
+ actualResult.benchmark,
+ percentile,
+ expectedPercentile.baseline,
+ expectedPercentile.difference,
+ actualPercentilesMap.get(percentile)));
+ }
+ }
+ }
+ }
+
+ public static double calculatePercentageDifference(double base, double compareWith) {
+ if (base == 0) {
+ return 0.0;
+ }
+ return ((compareWith - base) / base) * 100;
+ }
+
+ private String loadJsonFromFile(String file) {
+ try {
+ return new String(Files.readAllBytes(Paths.get(file)));
+ } catch (IOException e) {
+ throw new ValidationException("Failed to read file: " + file, e);
+ }
+ }
+
+ private String loadJsonFromResources(String baselineFile) {
+ URL resourceUrl = getClass().getClassLoader().getResource(baselineFile);
+ if (resourceUrl == null) {
+ throw new ValidationException("File not found: " + baselineFile);
+ }
+ File file = new File(resourceUrl.getFile());
+ return loadJsonFromFile(file.getAbsolutePath());
+ }
+
+ static class ActualBenchmarkResult {
+ String benchmark;
+ PrimaryMetric primaryMetric;
+
+ static class PrimaryMetric {
+ Map scorePercentiles;
+ }
+ }
+
+ static class BaselineResult {
+ Map benchmarkResultMap;
+
+ static class BenchmarkResult {
+ List scorePercentiles;
+
+ static class Percentile {
+ String percentile;
+ Double baseline;
+ Double difference;
+ }
+ }
+ }
+
+ static class ValidationException extends RuntimeException {
+ ValidationException(String message) {
+ super(message);
+ }
+
+ ValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ private static String parseCommandLineArgs(String[] args, String key) {
+ if (args == null) {
+ return "";
+ }
+ for (String arg : args) {
+ if (arg.startsWith("--" + key)) {
+ String[] splits = arg.split("=");
+ if (splits.length == 2) {
+ return splits[1].trim();
+ }
+ }
+ }
+ return "";
+ }
+
+ public static void main(String[] args) {
+ String actualFile = parseCommandLineArgs(args, "file");
+ new BenchmarkValidator("com/google/cloud/spanner/jmh/jmh-baseline.json", actualFile).validate();
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/MonitoringServiceImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/MonitoringServiceImpl.java
new file mode 100644
index 0000000000..aaa7387612
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/MonitoringServiceImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.benchmarking;
+
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.monitoring.v3.MetricServiceGrpc.MetricServiceImplBase;
+import com.google.protobuf.Empty;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+
+class MonitoringServiceImpl extends MetricServiceImplBase {
+
+ @Override
+ public void createServiceTimeSeries(
+ CreateTimeSeriesRequest request, StreamObserver responseObserver) {
+ try {
+ Thread.sleep(100);
+ responseObserver.onNext(Empty.getDefaultInstance());
+ responseObserver.onCompleted();
+ } catch (InterruptedException e) {
+ responseObserver.onError(
+ Status.CANCELLED.withCause(e).withDescription(e.getMessage()).asException());
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/ReadBenchmark.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/ReadBenchmark.java
new file mode 100644
index 0000000000..eed461fc89
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/benchmarking/ReadBenchmark.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.benchmarking;
+
+import com.google.cloud.NoCredentials;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.MockSpannerServiceImpl;
+import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.cloud.spanner.ReadContext;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import com.google.protobuf.ListValue;
+import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.StructType;
+import com.google.spanner.v1.StructType.Field;
+import com.google.spanner.v1.TypeCode;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Timeout;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.results.format.ResultFormatType;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+@BenchmarkMode(Mode.SampleTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@Threads(10)
+@Fork(1)
+public class ReadBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+
+ // Spanner state
+ Spanner spanner;
+ DatabaseClient databaseClient;
+
+ // gRPC server
+ Server gRPCServer;
+ Server gRPCMonitoringServer;
+
+ // Executors for handling parallel requests by gRPC server
+ ExecutorService gRPCServerExecutor;
+
+ // Table
+ List columns = Arrays.asList("id", "name");
+ String selectQuery = "SELECT * FROM [TABLE] WHERE ID = 1";
+
+ @Setup(Level.Trial)
+ public void setup() throws IOException {
+ // Enable JMH system property
+ System.setProperty("jmh.enabled", "true");
+
+ // Initializing mock spanner service
+ MockSpannerServiceImpl mockSpannerService = new MockSpannerServiceImpl();
+ mockSpannerService.setAbortProbability(0.0D);
+
+ // Initializing mock monitoring service
+ MonitoringServiceImpl mockMonitoringService = new MonitoringServiceImpl();
+
+ // Create a thread pool to handle concurrent requests
+ gRPCServerExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+ // Creating Spanner Inprocess gRPC server
+ gRPCServer =
+ ServerBuilder.forPort(0)
+ .addService(mockSpannerService)
+ .executor(gRPCServerExecutor)
+ .build()
+ .start();
+
+ registerMocks(mockSpannerService);
+
+ // Creating Monitoring Inprocess gRPC server
+ gRPCMonitoringServer =
+ ServerBuilder.forPort(0).addService(mockMonitoringService).build().start();
+
+ // Set the monitoring host port for exporter to forward requests to local netty gRPC server
+ System.setProperty(
+ "jmh.monitoring-server-port", String.valueOf(gRPCMonitoringServer.getPort()));
+
+ spanner =
+ SpannerOptions.newBuilder()
+ .setProjectId("[PROJECT]")
+ .setCredentials(NoCredentials.getInstance())
+ .setChannelConfigurator(
+ managedChannelBuilder ->
+ ManagedChannelBuilder.forAddress("0.0.0.0", gRPCServer.getPort())
+ .usePlaintext())
+ .build()
+ .getService();
+ databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE_ID]", "[DATABASE_ID]"));
+ }
+
+ private void registerMocks(MockSpannerServiceImpl mockSpannerService) {
+ ResultSetMetadata selectMetadata =
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("id")
+ .setType(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.INT64)
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("name")
+ .setType(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.STRING)
+ .build())
+ .build())
+ .build())
+ .build();
+ com.google.spanner.v1.ResultSet selectResultSet =
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(com.google.protobuf.Value.newBuilder().setStringValue("1").build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setStringValue("[NAME]").build())
+ .build())
+ .setMetadata(selectMetadata)
+ .build();
+ mockSpannerService.putStatementResult(
+ StatementResult.read(
+ "[TABLE]", KeySet.singleKey(Key.of()), this.columns, selectResultSet));
+ mockSpannerService.putStatementResult(
+ StatementResult.query(Statement.of(this.selectQuery), selectResultSet));
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() throws InterruptedException {
+ spanner.close();
+ gRPCServer.shutdown();
+ gRPCServerExecutor.shutdown();
+
+ // awaiting termination for servers and executors
+ gRPCServer.awaitTermination(10, TimeUnit.SECONDS);
+ gRPCServerExecutor.awaitTermination(10, TimeUnit.SECONDS);
+ }
+ }
+
+ @Benchmark
+ @Warmup(time = 5, timeUnit = TimeUnit.MINUTES, iterations = 1)
+ @Measurement(time = 15, timeUnit = TimeUnit.MINUTES, iterations = 1)
+ @Timeout(time = 30, timeUnit = TimeUnit.MINUTES)
+ public void readBenchmark(BenchmarkState benchmarkState, Blackhole blackhole) {
+ try (ReadContext readContext = benchmarkState.databaseClient.singleUse()) {
+ try (ResultSet resultSet =
+ readContext.read("[TABLE]", KeySet.singleKey(Key.of("2")), benchmarkState.columns)) {
+ while (resultSet.next()) {
+ blackhole.consume(resultSet.getLong("id"));
+ }
+ }
+ }
+ }
+
+ @Benchmark
+ @Warmup(time = 5, timeUnit = TimeUnit.MINUTES, iterations = 1)
+ @Measurement(time = 15, timeUnit = TimeUnit.MINUTES, iterations = 1)
+ @Timeout(time = 30, timeUnit = TimeUnit.MINUTES)
+ public void queryBenchmark(BenchmarkState benchmarkState, Blackhole blackhole) {
+ try (ReadContext readContext = benchmarkState.databaseClient.singleUse()) {
+ try (ResultSet resultSet =
+ readContext.executeQuery(Statement.of(benchmarkState.selectQuery))) {
+ while (resultSet.next()) {
+ blackhole.consume(resultSet.getLong("id"));
+ }
+ }
+ }
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt =
+ new OptionsBuilder()
+ .include(ReadBenchmark.class.getSimpleName())
+ .result("jmh-result.json")
+ .resultFormat(ResultFormatType.JSON)
+ .build();
+ new Runner(opt).run();
+ }
+}
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 e9af95d9de..18fd5cb614 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
@@ -436,6 +436,8 @@ public void testBuilderSetUri() {
"cloudspanner://spanner.googleapis.com/projects/test-project-123/instances/test-instance?autocommit=true;readonly=false");
builder.setUri(
"cloudspanner://spanner.googleapis.com/projects/test-project-123?autocommit=true;readonly=false");
+ builder.setUri(
+ "cloudspanner://spanner.googleapis.com/projects/test-project-123?statement_timeout='10s';transaction_timeout='60s'");
// set invalid uri's
setInvalidUri(
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/GrpcInterceptorProviderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/GrpcInterceptorProviderTest.java
new file mode 100644
index 0000000000..0845d1d9c3
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/GrpcInterceptorProviderTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.connection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.SpannerException;
+import com.google.common.collect.ImmutableList;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.MethodDescriptor;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class GrpcInterceptorProviderTest extends AbstractMockServerTest {
+ private static final AtomicBoolean INTERCEPTOR_CALLED = new AtomicBoolean(false);
+
+ public static final class TestGrpcInterceptorProvider implements GrpcInterceptorProvider {
+ @Override
+ public List getInterceptors() {
+ return ImmutableList.of(
+ new ClientInterceptor() {
+ @Override
+ public ClientCall interceptCall(
+ MethodDescriptor method, CallOptions callOptions, Channel next) {
+ INTERCEPTOR_CALLED.set(true);
+ return next.newCall(method, callOptions);
+ }
+ });
+ }
+ }
+
+ @Before
+ public void clearInterceptorUsedFlag() {
+ INTERCEPTOR_CALLED.set(false);
+ }
+
+ @Test
+ public void testGrpcInterceptorProviderIsNotUsedByDefault() {
+ assertFalse(INTERCEPTOR_CALLED.get());
+ try (Connection connection = createConnection()) {
+ try (ResultSet resultSet = connection.executeQuery(SELECT1_STATEMENT)) {
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ }
+ assertFalse(INTERCEPTOR_CALLED.get());
+ }
+
+ @Test
+ public void testGrpcInterceptorProviderIsUsedWhenConfigured() {
+ System.setProperty("ENABLE_GRPC_INTERCEPTOR_PROVIDER", "true");
+ assertFalse(INTERCEPTOR_CALLED.get());
+ try (Connection connection =
+ createConnection(
+ ";grpc_interceptor_provider=" + TestGrpcInterceptorProvider.class.getName())) {
+ try (ResultSet resultSet = connection.executeQuery(SELECT1_STATEMENT)) {
+ while (resultSet.next()) {
+ // ignore
+ }
+ }
+ } finally {
+ System.clearProperty("ENABLE_GRPC_INTERCEPTOR_PROVIDER");
+ }
+ assertTrue(INTERCEPTOR_CALLED.get());
+ }
+
+ @Test
+ public void testGrpcInterceptorProviderRequiresSystemProperty() {
+ assertFalse(INTERCEPTOR_CALLED.get());
+ SpannerException exception =
+ assertThrows(
+ SpannerException.class,
+ () ->
+ createConnection(
+ ";grpc_interceptor_provider=" + TestGrpcInterceptorProvider.class.getName()));
+ assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
+ assertTrue(
+ exception.getMessage(),
+ exception
+ .getMessage()
+ .contains(
+ "grpc_interceptor_provider can only be used if the system property"
+ + " ENABLE_GRPC_INTERCEPTOR_PROVIDER has been set to true. Start the"
+ + " application with the JVM command line option"
+ + " -DENABLE_GRPC_INTERCEPTOR_PROVIDER=true"));
+ assertFalse(INTERCEPTOR_CALLED.get());
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java
index b1adb3861b..e854b3d9d9 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java
@@ -108,10 +108,12 @@ public static Object[] parameters() {
@Parameter
public StatementExecutorType statementExecutorType;
- protected ITConnection createConnection() {
+ protected ITConnection createConnection(String additionalUrlOptions) {
+ String urlSuffix =
+ ";trackSessionLeaks=false" + (additionalUrlOptions == null ? "" : additionalUrlOptions);
ConnectionOptions options =
ConnectionOptions.newBuilder()
- .setUri(getBaseUrl() + ";trackSessionLeaks=false")
+ .setUri(getBaseUrl() + urlSuffix)
.setStatementExecutorType(statementExecutorType)
.setConfigurator(
optionsConfigurator -> {
@@ -135,6 +137,10 @@ protected ITConnection createConnection() {
return createITConnection(options);
}
+ protected ITConnection createConnection() {
+ return createConnection("");
+ }
+
@Before
public void setup() {
// Set up a connection and get the dialect to ensure that the auto-detect-dialect query has
@@ -169,6 +175,22 @@ public void testTimeoutExceptionReadOnlyAutocommit() {
}
}
+ @Test
+ public void testUrlTimeoutExceptionReadOnlyAutocommit() {
+ mockSpanner.setExecuteStreamingSqlExecutionTime(
+ SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0));
+
+ try (Connection connection =
+ createConnection(";statement_timeout='" + TIMEOUT_FOR_SLOW_STATEMENTS + "ms'")) {
+ connection.setAutocommit(true);
+ connection.setReadOnly(true);
+ SpannerException e =
+ assertThrows(
+ SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT));
+ assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode());
+ }
+ }
+
@Test
public void testTimeoutExceptionReadOnlyAutocommitMultipleStatements() {
mockSpanner.setExecuteStreamingSqlExecutionTime(
@@ -277,6 +299,30 @@ public void testTimeoutExceptionReadWriteAutocommitMultipleStatements() {
}
}
+ @Test
+ public void testUrlStatementTimeoutOverrideToSucceed() {
+ mockSpanner.setExecuteStreamingSqlExecutionTime(
+ SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0));
+
+ try (Connection connection =
+ createConnection(";statement_timeout='" + TIMEOUT_FOR_SLOW_STATEMENTS + "ms'")) {
+ connection.setAutocommit(true);
+ for (int i = 0; i < 2; i++) {
+ SpannerException e =
+ assertThrows(
+ SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT));
+ assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode());
+ }
+
+ // Remove slow behavior and verify a fast query succeeds after overriding the timeout.
+ mockSpanner.removeAllExecutionTimes();
+ connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS);
+ try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) {
+ assertNotNull(rs);
+ }
+ }
+ }
+
@Test
public void testTimeoutExceptionReadWriteAutocommitSlowUpdate() {
mockSpanner.setExecuteSqlExecutionTime(
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java
index dbefb73294..76b5436aba 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java
@@ -56,6 +56,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Random;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -74,6 +75,8 @@ public class ITTransactionTest {
@ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv();
private static Database db;
private static DatabaseClient client;
+ private static Database largeMessageDb;
+ private static DatabaseClient largeMessageClient;
/** Sequence for assigning unique keys to test cases. */
private static int seq;
@@ -88,11 +91,31 @@ public static void setUpDatabase() {
+ " V INT64,"
+ ") PRIMARY KEY (K)");
client = env.getTestHelper().getDatabaseClient(db);
+
+ largeMessageDb =
+ env.getTestHelper()
+ .createTestDatabase(
+ "CREATE TABLE T ("
+ + " K STRING(MAX) NOT NULL,"
+ + " col0 BYTES(MAX),"
+ + " col1 BYTES(MAX),"
+ + " col2 BYTES(MAX),"
+ + " col3 BYTES(MAX),"
+ + " col4 BYTES(MAX),"
+ + " col5 BYTES(MAX),"
+ + " col6 BYTES(MAX),"
+ + " col7 BYTES(MAX),"
+ + " col8 BYTES(MAX),"
+ + " col9 BYTES(MAX),"
+ + ") PRIMARY KEY (K)");
+ largeMessageClient = env.getTestHelper().getDatabaseClient(largeMessageDb);
}
@Before
public void removeTestData() {
client.writeAtLeastOnce(Collections.singletonList(Mutation.delete("T", KeySet.all())));
+ largeMessageClient.writeAtLeastOnce(
+ Collections.singletonList(Mutation.delete("T", KeySet.all())));
}
private static String uniqueKey() {
@@ -561,6 +584,25 @@ public void testTxWithUncaughtError() {
}
}
+ @Test
+ public void testTxWithLargeMessageSize() {
+ int bytesPerColumn = 10000000; // 10MB
+ String key = uniqueKey();
+ Random random = new Random();
+ List mutations = new ArrayList();
+ Mutation.WriteBuilder builder = Mutation.newInsertOrUpdateBuilder("T").set("K").to(key);
+ for (int j = 0; j < 7; j++) {
+ byte[] data = new byte[bytesPerColumn];
+ random.nextBytes(data);
+ builder
+ .set("col" + j)
+ .to(com.google.cloud.spanner.Value.bytes(com.google.cloud.ByteArray.copyFrom(data)));
+ }
+ mutations.add(builder.build());
+ // This large message is under the 100MB limit.
+ largeMessageClient.write(mutations);
+ }
+
@Test
public void testTxWithUncaughtErrorAfterSuccessfulBegin() {
try {
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/jmh/jmh-baseline.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/jmh/jmh-baseline.json
new file mode 100644
index 0000000000..7753f173ee
--- /dev/null
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/jmh/jmh-baseline.json
@@ -0,0 +1,22 @@
+{
+ "benchmarkResultMap": {
+ "com.google.cloud.spanner.benchmarking.ReadBenchmark.queryBenchmark": {
+ "scorePercentiles": [
+ {
+ "percentile": "50.0",
+ "baseline": "450",
+ "difference": "15"
+ }
+ ]
+ },
+ "com.google.cloud.spanner.benchmarking.ReadBenchmark.readBenchmark": {
+ "scorePercentiles": [
+ {
+ "percentile": "50.0",
+ "baseline": "450",
+ "difference": "15"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
index a89656fcfc..649a7b4226 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.101.1
+ 6.102.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.101.1
+ 6.102.0
diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
index c5e56e818d..9d0e8cca71 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.101.1
+ 6.102.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.101.1
+ 6.102.0
diff --git a/grpc-google-cloud-spanner-executor-v1/pom.xml b/grpc-google-cloud-spanner-executor-v1/pom.xml
index f143922638..bba5a5cd4b 100644
--- a/grpc-google-cloud-spanner-executor-v1/pom.xml
+++ b/grpc-google-cloud-spanner-executor-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
grpc-google-cloud-spanner-executor-v1
- 6.101.1
+ 6.102.0
grpc-google-cloud-spanner-executor-v1
GRPC library for google-cloud-spanner
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml
index 0e2812e71f..ebec943989 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.101.1
+ 6.102.0
grpc-google-cloud-spanner-v1
GRPC library for grpc-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
diff --git a/pom.xml b/pom.xml
index 3ed2cc20b5..275fad8258 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloud
google-cloud-spanner-parent
pom
- 6.101.1
+ 6.102.0
Google Cloud Spanner Parent
https://github.com/googleapis/java-spanner
@@ -14,7 +14,7 @@
com.google.cloud
sdk-platform-java-config
- 3.52.2
+ 3.52.3
@@ -61,47 +61,47 @@
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-executor-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.101.1
+ 6.102.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.101.1
+ 6.102.0
com.google.cloud
google-cloud-spanner
- 6.101.1
+ 6.102.0
@@ -121,7 +121,7 @@
com.google.truth
truth
- 1.4.4
+ 1.4.5
test
@@ -153,7 +153,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.14.0
+ 3.14.1
1.8
1.8
diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml
index af692c6a4d..1d6d70f6ee 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.101.1
+ 6.102.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.101.1
+ 6.102.0
diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
index 2b129d2faf..66cba86dba 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.101.1
+ 6.102.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.101.1
+ 6.102.0
diff --git a/proto-google-cloud-spanner-executor-v1/pom.xml b/proto-google-cloud-spanner-executor-v1/pom.xml
index 557343a2e3..82f9dc550a 100644
--- a/proto-google-cloud-spanner-executor-v1/pom.xml
+++ b/proto-google-cloud-spanner-executor-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.101.1
+ 6.102.0
proto-google-cloud-spanner-executor-v1
Proto library for google-cloud-spanner
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml
index 4acd5bf612..12e8c39d2e 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.101.1
+ 6.102.0
proto-google-cloud-spanner-v1
PROTO library for proto-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.101.1
+ 6.102.0
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index e24840bbb8..a4efda2e16 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -23,8 +23,8 @@
1.8
UTF-8
0.31.1
- 2.62.0
- 3.63.0
+ 2.76.0
+ 3.77.0
@@ -33,7 +33,7 @@
com.google.cloud
google-cloud-spanner
- 6.99.0
+ 6.101.1
@@ -100,7 +100,7 @@
com.google.truth
truth
- 1.4.4
+ 1.4.5
test
@@ -116,7 +116,7 @@
org.codehaus.mojo
build-helper-maven-plugin
- 3.6.0
+ 3.6.1
add-snippets-source
@@ -145,7 +145,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.5.3
+ 3.5.4
10
false
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index b8d534a4c8..f94f84eb0b 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -23,8 +23,8 @@
1.8
UTF-8
0.31.1
- 2.62.0
- 3.63.0
+ 2.76.0
+ 3.77.0
@@ -32,7 +32,7 @@
com.google.cloud
google-cloud-spanner
- 6.101.1
+ 6.102.0
@@ -99,7 +99,7 @@
com.google.truth
truth
- 1.4.4
+ 1.4.5
test
@@ -115,7 +115,7 @@
org.codehaus.mojo
build-helper-maven-plugin
- 3.6.0
+ 3.6.1
add-snippets-source
@@ -144,7 +144,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.5.3
+ 3.5.4
10
false
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 25be82edd8..5b2164f0cb 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -34,7 +34,7 @@
com.google.cloud
libraries-bom
- 26.68.0
+ 26.69.0
pom
import
@@ -111,7 +111,7 @@
com.google.truth
truth
- 1.4.4
+ 1.4.5
test
@@ -126,7 +126,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.5.3
+ 3.5.4
10
false
@@ -155,7 +155,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.5.3
+ 3.5.4
10
false
diff --git a/versions.txt b/versions.txt
index ee56d9a157..2ba0e25302 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,13 +1,13 @@
# Format:
# module:released-version:current-version
-proto-google-cloud-spanner-admin-instance-v1:6.101.1:6.101.1
-proto-google-cloud-spanner-v1:6.101.1:6.101.1
-proto-google-cloud-spanner-admin-database-v1:6.101.1:6.101.1
-grpc-google-cloud-spanner-v1:6.101.1:6.101.1
-grpc-google-cloud-spanner-admin-instance-v1:6.101.1:6.101.1
-grpc-google-cloud-spanner-admin-database-v1:6.101.1:6.101.1
-google-cloud-spanner:6.101.1:6.101.1
-google-cloud-spanner-executor:6.101.1:6.101.1
-proto-google-cloud-spanner-executor-v1:6.101.1:6.101.1
-grpc-google-cloud-spanner-executor-v1:6.101.1:6.101.1
+proto-google-cloud-spanner-admin-instance-v1:6.102.0:6.102.0
+proto-google-cloud-spanner-v1:6.102.0:6.102.0
+proto-google-cloud-spanner-admin-database-v1:6.102.0:6.102.0
+grpc-google-cloud-spanner-v1:6.102.0:6.102.0
+grpc-google-cloud-spanner-admin-instance-v1:6.102.0:6.102.0
+grpc-google-cloud-spanner-admin-database-v1:6.102.0:6.102.0
+google-cloud-spanner:6.102.0:6.102.0
+google-cloud-spanner-executor:6.102.0:6.102.0
+proto-google-cloud-spanner-executor-v1:6.102.0:6.102.0
+grpc-google-cloud-spanner-executor-v1:6.102.0:6.102.0