diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml
index 7146cc3dc1c..ab23b9fec09 100644
--- a/.github/workflows/hermetic_library_generation.yaml
+++ b/.github/workflows/hermetic_library_generation.yaml
@@ -17,10 +17,14 @@ name: Hermetic library generation upon generation config change through pull req
on:
pull_request:
+env:
+ HEAD_REF: ${{ github.head_ref }}
+ REPO_FULL_NAME: ${{ github.event.pull_request.head.repo.full_name }}
+
jobs:
library_generation:
# skip pull requests coming from a forked repository
- if: github.event.pull_request.head.repo.full_name == github.repository
+ if: github.env.REPO_FULL_NAME == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -30,11 +34,11 @@ jobs:
- name: Generate changed libraries
shell: bash
run: |
- set -x
+ set -ex
[ -z "$(git config user.email)" ] && git config --global user.email "cloud-java-bot@google.com"
[ -z "$(git config user.name)" ] && git config --global user.name "cloud-java-bot"
bash .github/scripts/hermetic_library_generation.sh \
--target_branch ${{ github.base_ref }} \
- --current_branch ${{ github.head_ref }}
+ --current_branch $HEAD_REF
env:
GH_TOKEN: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }}
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index 2e6ec1a12bf..e3d689d03c3 100644
--- a/.github/workflows/unmanaged_dependency_check.yaml
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -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.33.0
+ uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.34.0
with:
bom-path: google-cloud-spanner-bom/pom.xml
diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg
index 7008a721567..53cd15405a6 100644
--- a/.kokoro/presubmit/graalvm-native-17.cfg
+++ b/.kokoro/presubmit/graalvm-native-17.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.33.0"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.34.0"
}
env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index 931f9bb0052..e211e47fc69 100644
--- a/.kokoro/presubmit/graalvm-native.cfg
+++ b/.kokoro/presubmit/graalvm-native.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.33.0"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.34.0"
}
env_vars: {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc0757052a6..26f3fdf2fff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
# Changelog
+## [6.73.0](https://github.com/googleapis/java-spanner/compare/v6.72.0...v6.73.0) (2024-08-22)
+
+
+### Features
+
+* Add option for cancelling queries when closing client ([#3276](https://github.com/googleapis/java-spanner/issues/3276)) ([95da1ed](https://github.com/googleapis/java-spanner/commit/95da1eddbc979f4ce78c9d1ac15bc4c1faba6dca))
+
+
+### Bug Fixes
+
+* Github workflow vulnerable to script injection ([#3232](https://github.com/googleapis/java-spanner/issues/3232)) ([599255c](https://github.com/googleapis/java-spanner/commit/599255c36d1fbe8317705a7eeb2a9e400c3efd15))
+* Make DecodeMode.DIRECT the deafult ([#3280](https://github.com/googleapis/java-spanner/issues/3280)) ([f31a95a](https://github.com/googleapis/java-spanner/commit/f31a95ab105407305e988e86c8f7b0d8654995e0))
+* Synchronize lazy ResultSet decoding ([#3267](https://github.com/googleapis/java-spanner/issues/3267)) ([4219cf8](https://github.com/googleapis/java-spanner/commit/4219cf86dba5e44d55f13ab118113f119c92b9e9))
+
+
+### Dependencies
+
+* Update dependency com.google.cloud:sdk-platform-java-config to v3.34.0 ([#3277](https://github.com/googleapis/java-spanner/issues/3277)) ([c449a91](https://github.com/googleapis/java-spanner/commit/c449a91628b005481996bce5ab449d62496a4d2d))
+* Update dependency commons-cli:commons-cli to v1.9.0 ([#3275](https://github.com/googleapis/java-spanner/issues/3275)) ([84790f7](https://github.com/googleapis/java-spanner/commit/84790f7d437e88739487b148bf963f0ac9dc3f96))
+* Update dependency io.opentelemetry:opentelemetry-bom to v1.41.0 ([#3269](https://github.com/googleapis/java-spanner/issues/3269)) ([a7458e9](https://github.com/googleapis/java-spanner/commit/a7458e970e4ca55ff3e312b2129e890576145db1))
+* Update dependency org.hamcrest:hamcrest to v3 ([#3271](https://github.com/googleapis/java-spanner/issues/3271)) ([fc2e343](https://github.com/googleapis/java-spanner/commit/fc2e343dc06f80617a2cd6f2bea59b0631e70678))
+* Update dependency org.junit.vintage:junit-vintage-engine to v5.11.0 ([#3272](https://github.com/googleapis/java-spanner/issues/3272)) ([1bc0c46](https://github.com/googleapis/java-spanner/commit/1bc0c469b99ebf3778592b04dbf175b00bf5b06e))
+* Update opentelemetry.version to v1.41.0 ([#3270](https://github.com/googleapis/java-spanner/issues/3270)) ([88f6b56](https://github.com/googleapis/java-spanner/commit/88f6b56fb243bb17b814a7ae150c8f38dced119a))
+
+
+### Documentation
+
+* Create a few code snippets as examples for using Spanner Graph using Java ([#3234](https://github.com/googleapis/java-spanner/issues/3234)) ([61f0ab7](https://github.com/googleapis/java-spanner/commit/61f0ab7a48bc3e51b830534b1cfa70e40166ec91))
+
## [6.72.0](https://github.com/googleapis/java-spanner/compare/v6.71.0...v6.72.0) (2024-08-07)
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index ae700bd3372..23427597b8d 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -24,7 +24,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.72.0
+ 6.73.0
@@ -34,7 +34,7 @@
UTF-8
UTF-8
2.10.0
- 1.40.0
+ 1.41.0
@@ -85,19 +85,19 @@
io.opentelemetry
opentelemetry-bom
- 1.40.0
+ 1.41.0
pom
import
com.google.cloud
google-cloud-spanner
- 6.71.0
+ 6.72.0
commons-cli
commons-cli
- 1.8.0
+ 1.9.0
com.google.auto.value
@@ -118,7 +118,7 @@
commons-cli
commons-cli
- 1.8.0
+ 1.9.0
@@ -133,7 +133,7 @@
org.codehaus.mojo
exec-maven-plugin
- 3.4.0
+ 3.4.1
com.google.cloud.spanner.benchmark.LatencyBenchmark
false
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index fa9e6207a2c..a05a10d5a4d 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.72.0
+ 6.73.0
pom
com.google.cloud
sdk-platform-java-config
- 3.33.0
+ 3.34.0
Google Cloud Spanner BOM
@@ -53,43 +53,43 @@
com.google.cloud
google-cloud-spanner
- 6.72.0
+ 6.73.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.72.0
+ 6.73.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.72.0
+ 6.73.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.72.0
+ 6.73.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.72.0
+ 6.73.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.72.0
+ 6.73.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.72.0
+ 6.73.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.72.0
+ 6.73.0
diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml
index 3a99cd00624..35fc9e7c99d 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.72.0
+ 6.73.0
jar
Google Cloud Spanner Executor
com.google.cloud
google-cloud-spanner-parent
- 6.72.0
+ 6.73.0
@@ -129,7 +129,7 @@
commons-cli
commons-cli
- 1.8.0
+ 1.9.0
commons-io
@@ -188,7 +188,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
- 3.3.1
+ 3.4.0
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 4de2e277ffa..443a8faf238 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
@@ -75,6 +75,7 @@
import com.google.cloud.spanner.encryption.CustomerManagedEncryption;
import com.google.cloud.spanner.v1.stub.SpannerStubSettings;
import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -191,6 +192,16 @@ public CloudClientExecutor(boolean enableGrpcFaultInjector) {
this.enableGrpcFaultInjector = enableGrpcFaultInjector;
}
+ // Helper for unexpected results.
+ public static String unexpectedExceptionResponse(Exception e) {
+ return "Unexpected error in Github Cloud Java Client Executor: "
+ + e
+ + " Msg: "
+ + e.getMessage()
+ + " Stack: "
+ + Joiner.on("\n").join(e.getStackTrace());
+ }
+
/**
* Implementation of a ReadWriteTransaction, which is a wrapper of the cloud TransactionRunner. It
* stores all the status and related variables from the start to finish, and control the running
@@ -1083,7 +1094,7 @@ private Status executeCreateCloudInstance(
return sender.finishWithError(
toStatus(
SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "Unexpected error: " + e.getMessage())));
+ ErrorCode.INVALID_ARGUMENT, CloudClientExecutor.unexpectedExceptionResponse(e))));
}
return sender.finishWithOK();
}
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 5f42b91fd7e..59e7ed4a485 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.72.0
+ 6.73.0
jar
Google Cloud Spanner
https://github.com/googleapis/java-spanner
@@ -11,7 +11,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.72.0
+ 6.73.0
google-cloud-spanner
@@ -359,6 +359,12 @@
${graal-sdk.version}
provided
+
+ org.graalvm.sdk
+ nativeimage
+ ${graal-sdk.version}
+ provided
+
@@ -411,7 +417,7 @@
org.hamcrest
hamcrest
- 2.2
+ 3.0
test
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 92ebf006d29..caf0e06379e 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
@@ -36,6 +36,7 @@
import com.google.cloud.spanner.SessionClient.SessionOption;
import com.google.cloud.spanner.SessionImpl.SessionTransaction;
import com.google.cloud.spanner.spi.v1.SpannerRpc;
+import com.google.cloud.spanner.spi.v1.SpannerRpc.Option;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
@@ -45,7 +46,6 @@
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ExecuteSqlRequest;
-import com.google.spanner.v1.ExecuteSqlRequest.Builder;
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.PartialResultSet;
@@ -69,6 +69,7 @@ abstract class AbstractReadContext
abstract static class Builder, T extends AbstractReadContext> {
private SessionImpl session;
+ private boolean cancelQueryWhenClientIsClosed;
private SpannerRpc rpc;
private ISpan span;
private TraceWrapper tracer;
@@ -91,6 +92,11 @@ B setSession(SessionImpl session) {
return self();
}
+ B setCancelQueryWhenClientIsClosed(boolean cancelQueryWhenClientIsClosed) {
+ this.cancelQueryWhenClientIsClosed = cancelQueryWhenClientIsClosed;
+ return self();
+ }
+
B setRpc(SpannerRpc rpc) {
this.rpc = rpc;
return self();
@@ -184,7 +190,7 @@ static Builder newBuilder() {
@GuardedBy("lock")
private boolean used;
- private final Map channelHint;
+ private Map channelHint;
private SingleReadContext(Builder builder) {
super(builder);
@@ -227,6 +233,16 @@ TransactionSelector getTransactionSelector() {
Map getTransactionChannelHint() {
return channelHint;
}
+
+ @Override
+ boolean prepareRetryOnDifferentGrpcChannel() {
+ if (session.getIsMultiplexed() && channelHint.get(Option.CHANNEL_HINT) != null) {
+ long channelHintForTransaction = Option.CHANNEL_HINT.getLong(channelHint) + 1L;
+ channelHint = optionMap(SessionOption.channelHint(channelHintForTransaction));
+ return true;
+ }
+ return super.prepareRetryOnDifferentGrpcChannel();
+ }
}
private static void assertTimestampAvailable(boolean available) {
@@ -440,6 +456,7 @@ void initTransaction() {
final Object lock = new Object();
final SessionImpl session;
+ final boolean cancelQueryWhenClientIsClosed;
final SpannerRpc rpc;
final ExecutorProvider executorProvider;
ISpan span;
@@ -469,6 +486,7 @@ void initTransaction() {
AbstractReadContext(Builder, ?> builder) {
this.session = builder.session;
+ this.cancelQueryWhenClientIsClosed = builder.cancelQueryWhenClientIsClosed;
this.rpc = builder.rpc;
this.defaultPrefetchChunks = builder.defaultPrefetchChunks;
this.defaultQueryOptions = builder.defaultQueryOptions;
@@ -745,11 +763,13 @@ ResultSet executeQueryInternalWithOptions(
span,
tracer,
tracer.createStatementAttributes(statement, options),
+ session.getErrorHandler(),
rpc.getExecuteQueryRetrySettings(),
rpc.getExecuteQueryRetryableCodes()) {
@Override
CloseableIterator startStream(@Nullable ByteString resumeToken) {
- GrpcStreamIterator stream = new GrpcStreamIterator(statement, prefetchChunks);
+ GrpcStreamIterator stream =
+ new GrpcStreamIterator(statement, prefetchChunks, cancelQueryWhenClientIsClosed);
if (partitionToken != null) {
request.setPartitionToken(partitionToken);
}
@@ -774,6 +794,11 @@ CloseableIterator startStream(@Nullable ByteString resumeToken
stream.setCall(call, request.getTransaction().hasBegin());
return stream;
}
+
+ @Override
+ boolean prepareIteratorForRetryOnDifferentGrpcChannel() {
+ return AbstractReadContext.this.prepareRetryOnDifferentGrpcChannel();
+ }
};
return new GrpcResultSet(
stream, this, options.hasDecodeMode() ? options.decodeMode() : defaultDecodeMode);
@@ -840,6 +865,10 @@ public void close() {
*/
abstract Map getTransactionChannelHint();
+ boolean prepareRetryOnDifferentGrpcChannel() {
+ return false;
+ }
+
/**
* Returns the transaction tag for this {@link AbstractReadContext} or null
if this
* {@link AbstractReadContext} does not have a transaction tag.
@@ -918,11 +947,13 @@ ResultSet readInternalWithOptions(
SpannerImpl.READ,
span,
tracer,
+ session.getErrorHandler(),
rpc.getReadRetrySettings(),
rpc.getReadRetryableCodes()) {
@Override
CloseableIterator startStream(@Nullable ByteString resumeToken) {
- GrpcStreamIterator stream = new GrpcStreamIterator(prefetchChunks);
+ GrpcStreamIterator stream =
+ new GrpcStreamIterator(prefetchChunks, cancelQueryWhenClientIsClosed);
TransactionSelector selector = null;
if (resumeToken != null) {
builder.setResumeToken(resumeToken);
@@ -945,6 +976,11 @@ CloseableIterator startStream(@Nullable ByteString resumeToken
stream.setCall(call, /* withBeginTransaction = */ builder.getTransaction().hasBegin());
return stream;
}
+
+ @Override
+ boolean prepareIteratorForRetryOnDifferentGrpcChannel() {
+ return AbstractReadContext.this.prepareRetryOnDifferentGrpcChannel();
+ }
};
return new GrpcResultSet(
stream, this, readOptions.hasDecodeMode() ? readOptions.decodeMode() : defaultDecodeMode);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
index 22fb9f710c1..3d886dd383b 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
@@ -54,6 +54,7 @@ public BatchReadOnlyTransaction batchReadOnlyTransaction(TimestampBound bound) {
return new BatchReadOnlyTransactionImpl(
MultiUseReadOnlyTransaction.newBuilder()
.setSession(session)
+ .setCancelQueryWhenClientIsClosed(true)
.setRpc(sessionClient.getSpanner().getRpc())
.setTimestampBound(bound)
.setDefaultQueryOptions(
@@ -75,6 +76,7 @@ public BatchReadOnlyTransaction batchReadOnlyTransaction(BatchTransactionId batc
return new BatchReadOnlyTransactionImpl(
MultiUseReadOnlyTransaction.newBuilder()
.setSession(session)
+ .setCancelQueryWhenClientIsClosed(true)
.setRpc(sessionClient.getSpanner().getRpc())
.setTransactionId(batchTransactionId.getTransactionId())
.setTimestamp(batchTransactionId.getTimestamp())
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ErrorHandler.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ErrorHandler.java
new file mode 100644
index 00000000000..cf2465d7ade
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ErrorHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner;
+
+import com.google.api.core.BetaApi;
+import javax.annotation.Nonnull;
+
+/**
+ * The {@link ErrorHandler} interface can be used to implement custom error and retry handling for
+ * specific cases. The default implementation does nothing and falls back to the standard error and
+ * retry handling in Gax and the Spanner client.
+ */
+@BetaApi
+interface ErrorHandler {
+ @Nonnull
+ Throwable translateException(@Nonnull Throwable exception);
+
+ int getMaxAttempts();
+
+ class DefaultErrorHandler implements ErrorHandler {
+ static final DefaultErrorHandler INSTANCE = new DefaultErrorHandler();
+
+ private DefaultErrorHandler() {}
+
+ @Nonnull
+ @Override
+ public Throwable translateException(@Nonnull Throwable exception) {
+ return exception;
+ }
+
+ @Override
+ public int getMaxAttempts() {
+ return 0;
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
index 7b61901a60e..be75c1e5c4e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java
@@ -25,6 +25,7 @@
import com.google.spanner.v1.ResultSetMetadata;
import com.google.spanner.v1.ResultSetStats;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
@@ -35,6 +36,7 @@ class GrpcResultSet extends AbstractResultSet> implements ProtobufR
private final DecodeMode decodeMode;
private ResultSetMetadata metadata;
private GrpcStruct currRow;
+ private List