diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 3932a70d4e1..2425d7234f4 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -9,14 +9,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [8, 11]
+ java: [8, 11, 17]
steps:
- uses: actions/checkout@v2
- uses: stCarolas/setup-maven@v4
with:
maven-version: 3.8.1
- - uses: actions/setup-java@v1
+ - uses: actions/setup-java@v2
with:
+ distribution: zulu
java-version: ${{matrix.java}}
- run: java -version
- run: .kokoro/build.sh
@@ -29,8 +30,9 @@ jobs:
- uses: stCarolas/setup-maven@v4
with:
maven-version: 3.8.1
- - uses: actions/setup-java@v1
+ - uses: actions/setup-java@v2
with:
+ distribution: zulu
java-version: 8
- run: java -version
- run: .kokoro/build.bat
@@ -40,14 +42,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [8, 11]
+ java: [8, 11, 17]
steps:
- uses: actions/checkout@v2
- uses: stCarolas/setup-maven@v4
with:
maven-version: 3.8.1
- - uses: actions/setup-java@v1
+ - uses: actions/setup-java@v2
with:
+ distribution: zulu
java-version: ${{matrix.java}}
- run: java -version
- run: .kokoro/dependencies.sh
@@ -58,8 +61,9 @@ jobs:
- uses: stCarolas/setup-maven@v4
with:
maven-version: 3.8.1
- - uses: actions/setup-java@v1
+ - uses: actions/setup-java@v2
with:
+ distribution: zulu
java-version: 8
- run: java -version
- run: .kokoro/build.sh
@@ -72,8 +76,9 @@ jobs:
- uses: stCarolas/setup-maven@v4
with:
maven-version: 3.8.1
- - uses: actions/setup-java@v1
+ - uses: actions/setup-java@v2
with:
+ distribution: zulu
java-version: 8
- run: java -version
- run: .kokoro/build.sh
diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh
index 9030ba8f99f..d7476cfe972 100755
--- a/.kokoro/dependencies.sh
+++ b/.kokoro/dependencies.sh
@@ -28,7 +28,26 @@ source ${scriptDir}/common.sh
java -version
echo $JOB_TYPE
-export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=128m"
+function determineMavenOpts() {
+ local javaVersion=$(
+ # filter down to the version line, then pull out the version between quotes,
+ # then trim the version number down to its minimal number (removing any
+ # update or suffix number).
+ java -version 2>&1 | grep "version" \
+ | sed -E 's/^.*"(.*?)".*$/\1/g' \
+ | sed -E 's/^(1\.[0-9]\.0).*$/\1/g'
+ )
+
+ if [[ $javaVersion == 17* ]]
+ then
+ # MaxPermSize is no longer supported as of jdk 17
+ echo -n "-Xmx1024m"
+ else
+ echo -n "-Xmx1024m -XX:MaxPermSize=128m"
+ fi
+}
+
+export MAVEN_OPTS=$(determineMavenOpts)
# this should run maven enforcer
retry_with_backoff 3 10 \
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
new file mode 100644
index 00000000000..4c7225ec924
--- /dev/null
+++ b/.kokoro/presubmit/graalvm-native.cfg
@@ -0,0 +1,33 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Configure the docker image for kokoro-trampoline.
+env_vars: {
+ key: "TRAMPOLINE_IMAGE"
+ value: "gcr.io/cloud-devrel-kokoro-resources/graalvm"
+}
+
+env_vars: {
+ key: "JOB_TYPE"
+ value: "graalvm"
+}
+
+# TODO: remove this after we've migrated all tests and scripts
+env_vars: {
+ key: "GCLOUD_PROJECT"
+ value: "gcloud-devel"
+}
+
+env_vars: {
+ key: "GOOGLE_CLOUD_PROJECT"
+ value: "gcloud-devel"
+}
+
+env_vars: {
+ key: "GOOGLE_APPLICATION_CREDENTIALS"
+ value: "secret_manager/java-it-service-account"
+}
+
+env_vars: {
+ key: "SECRET_MANAGER_KEYS"
+ value: "java-it-service-account"
+}
diff --git a/.repo-metadata.json b/.repo-metadata.json
index adbaf69f81f..ddbcd2b6cd2 100644
--- a/.repo-metadata.json
+++ b/.repo-metadata.json
@@ -2,7 +2,7 @@
"name": "spanner",
"name_pretty": "Cloud Spanner",
"product_documentation": "https://cloud.google.com/spanner/docs/",
- "client_documentation": "https://googleapis.dev/java/google-cloud-spanner/latest/",
+ "client_documentation": "https://cloud.google.com/java/docs/reference/google-cloud-spanner/latest/history",
"api_description": "is a fully managed, mission-critical, \nrelational database service that offers transactional consistency at global scale, \nschemas, SQL (ANSI 2011 with extensions), and automatic, synchronous replication \nfor high availability.\n\nBe sure to activate the Cloud Spanner API on the Developer's Console to\nuse Cloud Spanner from your project.",
"issue_tracker": "https://issuetracker.google.com/issues?q=componentid:190851%2B%20status:open",
"release_level": "ga",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04b4aaeeaa2..3a54d4b44be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## [6.14.0](https://www.github.com/googleapis/java-spanner/compare/v6.13.0...v6.14.0) (2021-10-25)
+
+
+### Features
+
+* Introduce Native Image testing build script changes ([#1500](https://www.github.com/googleapis/java-spanner/issues/1500)) ([7a034c9](https://www.github.com/googleapis/java-spanner/commit/7a034c9120ffa433f64e67d565c854f1fb3ce9f5))
+
+
+### Bug Fixes
+
+* **java:** java 17 dependency arguments ([#1512](https://www.github.com/googleapis/java-spanner/issues/1512)) ([4cebefa](https://www.github.com/googleapis/java-spanner/commit/4cebefa1ce6502d48c2e2e0a3a484f60eeed450f))
+
+
+### Dependencies
+
+* update dependency com.google.cloud:google-cloud-monitoring to v3.1.0 ([#1506](https://www.github.com/googleapis/java-spanner/issues/1506)) ([ea35b27](https://www.github.com/googleapis/java-spanner/commit/ea35b2723fcc8c255ab0e52306e066c689c6a0c6))
+* update dependency com.google.cloud:google-cloud-shared-dependencies to v2.4.0 ([#1501](https://www.github.com/googleapis/java-spanner/issues/1501)) ([d5a37b8](https://www.github.com/googleapis/java-spanner/commit/d5a37b8853fc21a28b6610b2933ed31fcbe206e2))
+* update dependency com.google.cloud:google-cloud-trace to v2.0.6 ([#1504](https://www.github.com/googleapis/java-spanner/issues/1504)) ([667b8b1](https://www.github.com/googleapis/java-spanner/commit/667b8b17cc2f8d217ecda0af89bdc668670f3aab))
+
## [6.13.0](https://www.github.com/googleapis/java-spanner/compare/v6.12.5...v6.13.0) (2021-10-07)
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index a08766a6f48..3d9db44023c 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.13.0
+ 6.14.0
pom
com.google.cloud
google-cloud-shared-config
- 1.0.3
+ 1.2.0
Google Cloud Spanner BOM
@@ -54,43 +54,43 @@
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.13.0
+ 6.14.0
com.google.cloud
google-cloud-spanner
- 6.13.0
+ 6.14.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.13.0
+ 6.14.0
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index e870f315367..0b110f4a0a4 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.13.0
+ 6.14.0
jar
Google Cloud Spanner
https://github.com/googleapis/java-spanner
@@ -11,7 +11,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.13.0
+ 6.14.0
google-cloud-spanner
@@ -74,14 +74,27 @@
gcloud-devel
projects/gcloud-devel/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-key
- 6000
+ 3000
default
- com.google.cloud.spanner.SerialIntegrationTest, com.google.cloud.spanner.ParallelIntegrationTest
+ com.google.cloud.spanner.SerialIntegrationTest
+
+
+
+
+ parallel-integration-test
+
+ integration-test
+
+
+ com.google.cloud.spanner.ParallelIntegrationTest
+ 8
+ true
+ com.google.cloud.spanner.ParallelIntegrationTest
@@ -415,7 +428,7 @@
true
ipv4
- 6000
+ 3000
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java
index 48837d21cec..5559177d537 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java
@@ -16,50 +16,27 @@
package com.google.cloud.spanner.it;
-import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.api.gax.paging.Page;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.Backup;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.IntegrationTestEnv;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.ParallelIntegrationTest;
-import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
-import com.google.spanner.admin.database.v1.CreateBackupMetadata;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
-import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
-import io.grpc.CallOptions;
-import io.grpc.Channel;
-import io.grpc.ClientCall;
-import io.grpc.ClientInterceptor;
-import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
-import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
-import io.grpc.Metadata;
-import io.grpc.MethodDescriptor;
-import io.grpc.Status;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Random;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
@@ -72,6 +49,7 @@
@Category(ParallelIntegrationTest.class)
@RunWith(JUnit4.class)
public class ITDatabaseAdminTest {
+ private static final long TIMEOUT_MINUTES = 5;
@ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv();
private DatabaseAdminClient dbAdminClient;
private RemoteSpannerHelper testHelper;
@@ -98,7 +76,7 @@ public void databaseOperations() throws Exception {
String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)";
OperationFuture op =
dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1));
- Database db = op.get();
+ Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
dbs.add(db);
assertThat(db.getId().getDatabase()).isEqualTo(dbId);
@@ -119,7 +97,7 @@ public void databaseOperations() throws Exception {
String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)";
OperationFuture, ?> op2 =
dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), null);
- op2.get();
+ op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
List statementsInDb = dbAdminClient.getDatabaseDdl(instanceId, dbId);
assertThat(statementsInDb).containsExactly(statement1, statement2);
@@ -140,15 +118,15 @@ public void updateDdlRetry() throws Exception {
String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)";
OperationFuture op =
dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1));
- Database db = op.get();
+ Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
dbs.add(db);
String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)";
OperationFuture op1 =
dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), "myop");
OperationFuture op2 =
dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), "myop");
- op1.get();
- op2.get();
+ op1.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
// Remove the progress list from the metadata before comparing, as there could be small
// differences between the two in the reported progress depending on exactly when each
@@ -167,7 +145,7 @@ public void databaseOperationsViaEntity() throws Exception {
String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)";
OperationFuture op =
dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1));
- Database db = op.get();
+ Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
dbs.add(db);
assertThat(db.getId().getDatabase()).isEqualTo(dbId);
@@ -176,7 +154,7 @@ public void databaseOperationsViaEntity() throws Exception {
String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)";
OperationFuture, ?> op2 = db.updateDdl(ImmutableList.of(statement2), null);
- op2.get();
+ op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES);
Iterable statementsInDb = db.getDdl();
assertThat(statementsInDb).containsExactly(statement1, statement2);
db.drop();
@@ -216,217 +194,4 @@ public void listPagination() throws Exception {
}
assertThat(dbIdsGot).containsAtLeastElementsIn(dbIds);
}
-
- private static final class InjectErrorInterceptorProvider implements GrpcInterceptorProvider {
- final AtomicBoolean injectError = new AtomicBoolean(true);
- final AtomicInteger getOperationCount = new AtomicInteger();
- final AtomicInteger methodCount = new AtomicInteger();
- final String methodName;
-
- private InjectErrorInterceptorProvider(String methodName) {
- this.methodName = methodName;
- }
-
- @Override
- public List getInterceptors() {
- ClientInterceptor interceptor =
- new ClientInterceptor() {
- @Override
- public ClientCall interceptCall(
- MethodDescriptor method, CallOptions callOptions, Channel next) {
- if (method.getFullMethodName().contains("GetOperation")) {
- getOperationCount.incrementAndGet();
- }
- if (!method.getFullMethodName().contains(methodName)) {
- return next.newCall(method, callOptions);
- }
-
- methodCount.incrementAndGet();
- final AtomicBoolean errorInjected = new AtomicBoolean();
- final ClientCall clientCall = next.newCall(method, callOptions);
-
- return new SimpleForwardingClientCall(clientCall) {
- @Override
- public void start(Listener responseListener, Metadata headers) {
- super.start(
- new SimpleForwardingClientCallListener(responseListener) {
- @Override
- public void onMessage(RespT message) {
- if (injectError.getAndSet(false)) {
- errorInjected.set(true);
- clientCall.cancel("Cancelling call for injected error", null);
- } else {
- super.onMessage(message);
- }
- }
-
- @Override
- public void onClose(Status status, Metadata metadata) {
- if (errorInjected.get()) {
- status = Status.UNAVAILABLE.augmentDescription("INJECTED BY TEST");
- }
- super.onClose(status, metadata);
- }
- },
- headers);
- }
- };
- }
- };
- return Collections.singletonList(interceptor);
- }
- }
-
- @Test
- public void testRetryNonIdempotentRpcsReturningLongRunningOperations() throws Exception {
- assumeFalse(
- "Querying long-running operations is not supported on the emulator", isUsingEmulator());
-
- // RPCs that return a long-running operation such as CreateDatabase, CreateBackup and
- // RestoreDatabase are non-idempotent and can normally not be automatically retried in case of a
- // transient failure. The client library will however automatically query the backend to check
- // whether the corresponding operation was started or not, and if it was, it will pick up the
- // existing operation. If no operation is found, a new RPC call will be executed to start the
- // operation.
-
- List databases = new ArrayList<>();
- List backups = new ArrayList<>();
- String initialDatabaseId;
- Timestamp initialDbCreateTime;
-
- try {
- // CreateDatabase
- InjectErrorInterceptorProvider createDbInterceptor =
- new InjectErrorInterceptorProvider("CreateDatabase");
- SpannerOptions options =
- testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build();
- try (Spanner spanner = options.getService()) {
- initialDatabaseId = testHelper.getUniqueDatabaseId();
- DatabaseAdminClient client = spanner.getDatabaseAdminClient();
- OperationFuture op =
- client.createDatabase(
- testHelper.getInstanceId().getInstance(),
- initialDatabaseId,
- Collections.emptyList());
- databases.add(op.get());
- // Keep track of the original create time of this database, as we will drop this database
- // later and create another one with the exact same name. That means that the ListOperations
- // call will return at least two CreateDatabase operations. The retry logic should always
- // pick the last one.
- initialDbCreateTime = op.get().getCreateTime();
- // Assert that the CreateDatabase RPC was called only once, and that the operation tracking
- // was resumed through a GetOperation call.
- assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
- assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
- }
-
- // CreateBackup
- InjectErrorInterceptorProvider createBackupInterceptor =
- new InjectErrorInterceptorProvider("CreateBackup");
- options =
- testHelper
- .getOptions()
- .toBuilder()
- .setInterceptorProvider(createBackupInterceptor)
- .build();
- try (Spanner spanner = options.getService()) {
- String databaseId = databases.get(0).getId().getDatabase();
- String backupId = String.format("test-bck-%08d", new Random().nextInt(100000000));
- DatabaseAdminClient client = spanner.getDatabaseAdminClient();
- OperationFuture op =
- client.createBackup(
- testHelper.getInstanceId().getInstance(),
- backupId,
- databaseId,
- Timestamp.ofTimeSecondsAndNanos(
- Timestamp.now().getSeconds() + TimeUnit.SECONDS.convert(7L, TimeUnit.DAYS), 0));
- backups.add(op.get());
- // Assert that the CreateBackup RPC was called only once, and that the operation tracking
- // was resumed through a GetOperation call.
- assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
- assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
- }
-
- // RestoreBackup
- int attempts = 0;
- while (true) {
- InjectErrorInterceptorProvider restoreBackupInterceptor =
- new InjectErrorInterceptorProvider("RestoreBackup");
- options =
- testHelper
- .getOptions()
- .toBuilder()
- .setInterceptorProvider(restoreBackupInterceptor)
- .build();
- try (Spanner spanner = options.getService()) {
- String backupId = backups.get(0).getId().getBackup();
- String restoredDbId = testHelper.getUniqueDatabaseId();
- DatabaseAdminClient client = spanner.getDatabaseAdminClient();
- OperationFuture op =
- client.restoreDatabase(
- testHelper.getInstanceId().getInstance(),
- backupId,
- testHelper.getInstanceId().getInstance(),
- restoredDbId);
- databases.add(op.get());
- // Assert that the RestoreDatabase RPC was called only once, and that the operation
- // tracking was resumed through a GetOperation call.
- assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
- assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
- break;
- } catch (ExecutionException e) {
- if (e.getCause() instanceof SpannerException
- && ((SpannerException) e.getCause()).getErrorCode() == ErrorCode.FAILED_PRECONDITION
- && e.getCause()
- .getMessage()
- .contains("Please retry the operation once the pending restores complete")) {
- attempts++;
- if (attempts == 10) {
- // Still same error after 10 attempts. Ignore.
- break;
- }
- // wait and then retry.
- Thread.sleep(60_000L);
- } else {
- throw e;
- }
- }
- }
-
- // Create another database with the exact same name as the first database.
- createDbInterceptor = new InjectErrorInterceptorProvider("CreateDatabase");
- options =
- testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build();
- try (Spanner spanner = options.getService()) {
- DatabaseAdminClient client = spanner.getDatabaseAdminClient();
- // First drop the initial database.
- client.dropDatabase(testHelper.getInstanceId().getInstance(), initialDatabaseId);
- // Now re-create a database with the exact same name.
- OperationFuture op =
- client.createDatabase(
- testHelper.getInstanceId().getInstance(),
- initialDatabaseId,
- Collections.emptyList());
- // Check that the second database was created and has a greater creation time than the
- // first.
- Timestamp secondCreationTime = op.get().getCreateTime();
- // TODO: Change this to greaterThan when the create time of a database is reported back by
- // the server.
- assertThat(secondCreationTime).isAtLeast(initialDbCreateTime);
- // Assert that the CreateDatabase RPC was called only once, and that the operation tracking
- // was resumed through a GetOperation call.
- assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
- assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
- }
- } finally {
- DatabaseAdminClient client = testHelper.getClient().getDatabaseAdminClient();
- for (Database database : databases) {
- client.dropDatabase(
- database.getId().getInstanceId().getInstance(), database.getId().getDatabase());
- }
- for (Backup backup : backups) {
- client.deleteBackup(backup.getInstanceId().getInstance(), backup.getId().getBackup());
- }
- }
- }
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java
index d1c09c247df..fa756b2f277 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java
@@ -51,7 +51,7 @@
@RunWith(JUnit4.class)
public class ITPitrUpdateDatabaseTest {
- private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(20);
private static final String VERSION_RETENTION_PERIOD = "7d";
@ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv();
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java
index 672f6551e99..8bcaa1c54cf 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assume.assumeFalse;
import com.google.api.client.util.Lists;
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.api.gax.paging.Page;
import com.google.api.gax.rpc.FailedPreconditionException;
@@ -45,8 +46,10 @@
import com.google.cloud.spanner.Restore;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SlowTest;
+import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.encryption.EncryptionConfigs;
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
@@ -59,15 +62,26 @@
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
import com.google.spanner.admin.database.v1.RestoreSourceType;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
import io.grpc.Status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -88,6 +102,9 @@
@Category(SlowTest.class)
@RunWith(JUnit4.class)
public class ITBackupTest {
+ private static final long DATABASE_TIMEOUT_MINUTES = 5;
+ private static final long RESTORE_TIMEOUT_MINUTES = 20;
+ private static final long BACKUP_TIMEOUT_MINUTES = 30;
private static final Logger logger = Logger.getLogger(ITBackupTest.class.getName());
private static final String EXPECTED_OP_NAME_FORMAT = "%s/backups/%s/operations/";
private static final String KMS_KEY_NAME_PROPERTY = "spanner.testenv.kms_key.name";
@@ -206,7 +223,7 @@ private void waitForDbOperations(String backupId) throws InterruptedException {
}
@Test
- public void testBackups() throws InterruptedException, ExecutionException {
+ public void testBackups() throws InterruptedException, ExecutionException, TimeoutException {
// Create two test databases in parallel.
final String db1Id = testHelper.getUniqueDatabaseId() + "_db1";
final Database sourceDatabase1 =
@@ -229,8 +246,8 @@ public void testBackups() throws InterruptedException, ExecutionException {
Collections.singletonList(
"CREATE TABLE BAR (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)"));
// Make sure all databases are created before we try to create any backups.
- Database db1 = dbOp1.get();
- Database db2 = dbOp2.get();
+ Database db1 = dbOp1.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ Database db2 = dbOp2.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES);
databases.add(db1.getId().getDatabase());
databases.add(db2.getId().getDatabase());
// Insert some data into db2 to make sure the backup will have a size>0.
@@ -382,6 +399,141 @@ public void testBackups() throws InterruptedException, ExecutionException {
logger.info("Finished all backup tests");
}
+ @Test
+ public void testRetryNonIdempotentRpcsReturningLongRunningOperations() throws Exception {
+ assumeFalse(
+ "Querying long-running operations is not supported on the emulator", isUsingEmulator());
+
+ // RPCs that return a long-running operation such as CreateDatabase, CreateBackup and
+ // RestoreDatabase are non-idempotent and can normally not be automatically retried in case of a
+ // transient failure. The client library will however automatically query the backend to check
+ // whether the corresponding operation was started or not, and if it was, it will pick up the
+ // existing operation. If no operation is found, a new RPC call will be executed to start the
+ // operation.
+
+ List databases = new ArrayList<>();
+ List backups = new ArrayList<>();
+ String initialDatabaseId;
+ Timestamp initialDbCreateTime;
+
+ // CreateDatabase
+ InjectErrorInterceptorProvider createDbInterceptor =
+ new InjectErrorInterceptorProvider("CreateDatabase");
+ SpannerOptions options =
+ testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build();
+ try (Spanner spanner = options.getService()) {
+ initialDatabaseId = testHelper.getUniqueDatabaseId();
+ DatabaseAdminClient client = spanner.getDatabaseAdminClient();
+ OperationFuture op =
+ client.createDatabase(
+ testHelper.getInstanceId().getInstance(), initialDatabaseId, Collections.emptyList());
+ databases.add(op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES));
+ // Keep track of the original create time of this database, as we will drop this database
+ // later and create another one with the exact same name. That means that the ListOperations
+ // call will return at least two CreateDatabase operations. The retry logic should always
+ // pick the last one.
+ initialDbCreateTime = op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES).getCreateTime();
+ // Assert that the CreateDatabase RPC was called only once, and that the operation tracking
+ // was resumed through a GetOperation call.
+ assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
+ assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
+ }
+
+ // CreateBackup
+ InjectErrorInterceptorProvider createBackupInterceptor =
+ new InjectErrorInterceptorProvider("CreateBackup");
+ options =
+ testHelper.getOptions().toBuilder().setInterceptorProvider(createBackupInterceptor).build();
+ try (Spanner spanner = options.getService()) {
+ String databaseId = databases.get(0).getId().getDatabase();
+ String backupId = String.format("test-bck-%08d", new Random().nextInt(100000000));
+ DatabaseAdminClient client = spanner.getDatabaseAdminClient();
+ OperationFuture op =
+ client.createBackup(
+ testHelper.getInstanceId().getInstance(),
+ backupId,
+ databaseId,
+ Timestamp.ofTimeSecondsAndNanos(
+ Timestamp.now().getSeconds() + TimeUnit.SECONDS.convert(7L, TimeUnit.DAYS), 0));
+ backups.add(op.get(BACKUP_TIMEOUT_MINUTES, TimeUnit.MINUTES));
+ // Assert that the CreateBackup RPC was called only once, and that the operation tracking
+ // was resumed through a GetOperation call.
+ assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
+ assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
+ }
+
+ // RestoreBackup
+ int attempts = 0;
+ while (true) {
+ InjectErrorInterceptorProvider restoreBackupInterceptor =
+ new InjectErrorInterceptorProvider("RestoreBackup");
+ options =
+ testHelper
+ .getOptions()
+ .toBuilder()
+ .setInterceptorProvider(restoreBackupInterceptor)
+ .build();
+ try (Spanner spanner = options.getService()) {
+ String backupId = backups.get(0).getId().getBackup();
+ String restoredDbId = testHelper.getUniqueDatabaseId();
+ DatabaseAdminClient client = spanner.getDatabaseAdminClient();
+ OperationFuture op =
+ client.restoreDatabase(
+ testHelper.getInstanceId().getInstance(),
+ backupId,
+ testHelper.getInstanceId().getInstance(),
+ restoredDbId);
+ databases.add(op.get(RESTORE_TIMEOUT_MINUTES, TimeUnit.MINUTES));
+ // Assert that the RestoreDatabase RPC was called only once, and that the operation
+ // tracking was resumed through a GetOperation call.
+ assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
+ assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
+ break;
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof SpannerException
+ && ((SpannerException) e.getCause()).getErrorCode() == ErrorCode.FAILED_PRECONDITION
+ && e.getCause()
+ .getMessage()
+ .contains("Please retry the operation once the pending restores complete")) {
+ attempts++;
+ if (attempts == 10) {
+ // Still same error after 10 attempts. Ignore.
+ break;
+ }
+ // wait and then retry.
+ Thread.sleep(60_000L);
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Create another database with the exact same name as the first database.
+ createDbInterceptor = new InjectErrorInterceptorProvider("CreateDatabase");
+ options =
+ testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build();
+ try (Spanner spanner = options.getService()) {
+ DatabaseAdminClient client = spanner.getDatabaseAdminClient();
+ // First drop the initial database.
+ client.dropDatabase(testHelper.getInstanceId().getInstance(), initialDatabaseId);
+ // Now re-create a database with the exact same name.
+ OperationFuture op =
+ client.createDatabase(
+ testHelper.getInstanceId().getInstance(), initialDatabaseId, Collections.emptyList());
+ // Check that the second database was created and has a greater creation time than the
+ // first.
+ Timestamp secondCreationTime =
+ op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES).getCreateTime();
+ // TODO: Change this to greaterThan when the create time of a database is reported back by
+ // the server.
+ assertThat(secondCreationTime).isAtLeast(initialDbCreateTime);
+ // Assert that the CreateDatabase RPC was called only once, and that the operation tracking
+ // was resumed through a GetOperation call.
+ assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1);
+ assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1);
+ }
+ }
+
@Test(expected = SpannerException.class)
public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception {
final Database testDatabase = testHelper.createTestDatabase();
@@ -749,4 +901,64 @@ private void verifyRestoreOperations(
input -> input.getName().equals(restoreOperationName)))
.isTrue();
}
+
+ private static final class InjectErrorInterceptorProvider implements GrpcInterceptorProvider {
+ final AtomicBoolean injectError = new AtomicBoolean(true);
+ final AtomicInteger getOperationCount = new AtomicInteger();
+ final AtomicInteger methodCount = new AtomicInteger();
+ final String methodName;
+
+ private InjectErrorInterceptorProvider(String methodName) {
+ this.methodName = methodName;
+ }
+
+ @Override
+ public List getInterceptors() {
+ ClientInterceptor interceptor =
+ new ClientInterceptor() {
+ @Override
+ public ClientCall interceptCall(
+ MethodDescriptor method, CallOptions callOptions, Channel next) {
+ if (method.getFullMethodName().contains("GetOperation")) {
+ getOperationCount.incrementAndGet();
+ }
+ if (!method.getFullMethodName().contains(methodName)) {
+ return next.newCall(method, callOptions);
+ }
+
+ methodCount.incrementAndGet();
+ final AtomicBoolean errorInjected = new AtomicBoolean();
+ final ClientCall clientCall = next.newCall(method, callOptions);
+
+ return new SimpleForwardingClientCall(clientCall) {
+ @Override
+ public void start(Listener responseListener, Metadata headers) {
+ super.start(
+ new SimpleForwardingClientCallListener(responseListener) {
+ @Override
+ public void onMessage(RespT message) {
+ if (injectError.getAndSet(false)) {
+ errorInjected.set(true);
+ clientCall.cancel("Cancelling call for injected error", null);
+ } else {
+ super.onMessage(message);
+ }
+ }
+
+ @Override
+ public void onClose(Status status, Metadata metadata) {
+ if (errorInjected.get()) {
+ status = Status.UNAVAILABLE.augmentDescription("INJECTED BY TEST");
+ }
+ super.onClose(status, metadata);
+ }
+ },
+ headers);
+ }
+ };
+ }
+ };
+ return Collections.singletonList(interceptor);
+ }
+ }
}
diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
index c5a9067fba1..9908058de37 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.13.0
+ 6.14.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.13.0
+ 6.14.0
diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
index 14e7d776641..99aeb7cd43f 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.13.0
+ 6.14.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.13.0
+ 6.14.0
diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml
index ec67266f4c7..f37c72aadf3 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.13.0
+ 6.14.0
grpc-google-cloud-spanner-v1
GRPC library for grpc-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.13.0
+ 6.14.0
diff --git a/pom.xml b/pom.xml
index e12a475672c..ab56795501f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloud
google-cloud-spanner-parent
pom
- 6.13.0
+ 6.14.0
Google Cloud Spanner Parent
https://github.com/googleapis/java-spanner
@@ -14,7 +14,7 @@
com.google.cloud
google-cloud-shared-config
- 1.0.3
+ 1.2.0
@@ -54,7 +54,7 @@
UTF-8
github
google-cloud-spanner-parent
- 2.3.0
+ 2.4.0
@@ -62,37 +62,37 @@
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.13.0
+ 6.14.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.13.0
+ 6.14.0
com.google.cloud
google-cloud-spanner
- 6.13.0
+ 6.14.0
diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml
index 205821a9883..c7df8eb8796 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.13.0
+ 6.14.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.13.0
+ 6.14.0
diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
index b1eefd5afe7..e7086bbbead 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.13.0
+ 6.14.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.13.0
+ 6.14.0
diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml
index 1aefbca8a9c..72c1229c01c 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.13.0
+ 6.14.0
proto-google-cloud-spanner-v1
PROTO library for proto-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.13.0
+ 6.14.0
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 093486551d2..6c22d293eff 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -22,8 +22,8 @@
1.8
UTF-8
0.28.3
- 2.0.5
- 3.0.7
+ 2.0.6
+ 3.1.0
@@ -32,7 +32,7 @@
com.google.cloud
google-cloud-spanner
- 6.12.5
+ 6.13.0
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index f54e71a2fa0..f05c416264a 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -22,8 +22,8 @@
1.8
UTF-8
0.28.3
- 2.0.5
- 3.0.7
+ 2.0.6
+ 3.1.0
@@ -31,7 +31,7 @@
com.google.cloud
google-cloud-spanner
- 6.13.0
+ 6.14.0
diff --git a/samples/snippets/src/main/java/com/example/spanner/TagSample.java b/samples/snippets/src/main/java/com/example/spanner/TagSample.java
new file mode 100644
index 00000000000..d8124d3ae26
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/TagSample.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 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.example.spanner;
+
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.Options;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Statement;
+
+/**
+ * Sample showing how to add transaction and query tags to Cloud Spanner operations.
+ */
+public class TagSample {
+
+ // [START spanner_set_transaction_tag]
+ static void setTransactionTag(DatabaseClient databaseClient) {
+ // Sets the transaction tag to "app=concert,env=dev".
+ // This transaction tag will be applied to all the individual operations inside this
+ // transaction.
+ databaseClient
+ .readWriteTransaction(Options.tag("app=concert,env=dev"))
+ .run(transaction -> {
+ // Sets the request tag to "app=concert,env=dev,action=update".
+ // This request tag will only be set on this request.
+ transaction.executeUpdate(
+ Statement.of("UPDATE Venues"
+ + " SET Capacity = CAST(Capacity/4 AS INT64)"
+ + " WHERE OutdoorVenue = false"),
+ Options.tag("app=concert,env=dev,action=update"));
+ System.out.println("Venue capacities updated.");
+
+ Statement insertStatement = Statement.newBuilder(
+ "INSERT INTO Venues"
+ + " (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)"
+ + " VALUES ("
+ + " @venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP()"
+ + " )")
+ .bind("venueId")
+ .to(81)
+ .bind("venueName")
+ .to("Venue 81")
+ .bind("capacity")
+ .to(1440)
+ .bind("outdoorVenue")
+ .to(true)
+ .build();
+
+ // Sets the request tag to "app=concert,env=dev,action=insert".
+ // This request tag will only be set on this request.
+ transaction.executeUpdate(
+ insertStatement,
+ Options.tag("app=concert,env=dev,action=insert"));
+ System.out.println("New venue inserted.");
+
+ return null;
+ });
+ }
+ // [END spanner_set_transaction_tag]
+
+ // [START spanner_set_request_tag]
+ static void setRequestTag(DatabaseClient databaseClient) {
+ // Sets the request tag to "app=concert,env=dev,action=select".
+ // This request tag will only be set on this request.
+ try (ResultSet resultSet = databaseClient
+ .singleUse()
+ .executeQuery(
+ Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
+ Options.tag("app=concert,env=dev,action=select"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n",
+ resultSet.getLong(0),
+ resultSet.getLong(1),
+ resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_set_request_tag]
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java
new file mode 100644
index 00000000000..61733c1f0a4
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2021 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.example.spanner;
+
+import static com.example.spanner.SampleRunner.runSample;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for {@link TagSample}
+ */
+@RunWith(JUnit4.class)
+public class TagSampleIT extends SampleTestBase {
+
+ private static DatabaseId databaseId;
+
+ @BeforeClass
+ public static void createTestDatabase() throws Exception {
+ final String database = idGenerator.generateDatabaseId();
+ databaseAdminClient
+ .createDatabase(
+ instanceId,
+ database,
+ ImmutableList.of(
+ "CREATE TABLE Albums ("
+ + " SingerId INT64 NOT NULL,"
+ + " AlbumId INT64,"
+ + " AlbumTitle STRING(1024)"
+ + ") PRIMARY KEY (SingerId, AlbumId)",
+ "CREATE TABLE Venues ("
+ + " VenueId INT64 NOT NULL,"
+ + " VenueName STRING(MAX),"
+ + " Capacity INT64,"
+ + " OutdoorVenue BOOL,"
+ + " LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true)"
+ + ") PRIMARY KEY (VenueId)"))
+ .get(10, TimeUnit.MINUTES);
+ databaseId = DatabaseId.of(projectId, instanceId, database);
+ }
+
+ @Before
+ public void insertTestData() {
+ final DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ client.write(
+ Arrays.asList(
+ Mutation.newInsertOrUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1L)
+ .set("AlbumId")
+ .to(1L)
+ .set("AlbumTitle")
+ .to("title 1")
+ .build(),
+ Mutation.newInsertOrUpdateBuilder("Venues")
+ .set("VenueId")
+ .to(4L)
+ .set("VenueName")
+ .to("name")
+ .set("Capacity")
+ .to(4000000)
+ .set("OutdoorVenue")
+ .to(false)
+ .build()));
+ }
+
+ @After
+ public void removeTestData() {
+ final DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ client.write(Collections.singletonList(Mutation.delete("Albums", KeySet.all())));
+ client.write(Collections.singleton(Mutation.delete("Venues", KeySet.all())));
+ }
+
+ @Test
+ public void testSetRequestTag() throws Exception {
+ final DatabaseClient client = spanner.getDatabaseClient(databaseId);
+
+ final String out = runSample(() -> TagSample.setRequestTag(client));
+ assertTrue(out.contains("SingerId: 1, AlbumId: 1, AlbumTitle: title 1"));
+ }
+
+ @Test
+ public void testSetTransactionTag() throws Exception {
+ final DatabaseClient client = spanner.getDatabaseClient(databaseId);
+
+ final String out = runSample(() -> TagSample.setTransactionTag(client));
+ assertTrue(out.contains("Venue capacities updated."));
+ assertTrue(out.contains("New venue inserted."));
+ }
+}
diff --git a/synth.metadata b/synth.metadata
index 392814c1d12..a295fe17c93 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,7 +4,7 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/java-spanner.git",
- "sha": "06f98b174b785d61ad430b27a3a8216e3c874318"
+ "sha": "9083e827f82d0f11b97a311d9e2a1af7447fc1de"
}
},
{
@@ -19,7 +19,7 @@
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "0752ff727a19a467dffed335d5e59303689cf0d1"
+ "sha": "d45942be8066ad57bd0509f4a16e1fac78ecc50f"
}
}
],
@@ -79,6 +79,7 @@
".kokoro/populate-secrets.sh",
".kokoro/presubmit/clirr.cfg",
".kokoro/presubmit/dependencies.cfg",
+ ".kokoro/presubmit/graalvm-native.cfg",
".kokoro/presubmit/integration.cfg",
".kokoro/presubmit/java11.cfg",
".kokoro/presubmit/java7.cfg",
diff --git a/versions.txt b/versions.txt
index 87d31737895..ef14e5ba025 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,10 +1,10 @@
# Format:
# module:released-version:current-version
-proto-google-cloud-spanner-admin-instance-v1:6.13.0:6.13.0
-proto-google-cloud-spanner-v1:6.13.0:6.13.0
-proto-google-cloud-spanner-admin-database-v1:6.13.0:6.13.0
-grpc-google-cloud-spanner-v1:6.13.0:6.13.0
-grpc-google-cloud-spanner-admin-instance-v1:6.13.0:6.13.0
-grpc-google-cloud-spanner-admin-database-v1:6.13.0:6.13.0
-google-cloud-spanner:6.13.0:6.13.0
\ No newline at end of file
+proto-google-cloud-spanner-admin-instance-v1:6.14.0:6.14.0
+proto-google-cloud-spanner-v1:6.14.0:6.14.0
+proto-google-cloud-spanner-admin-database-v1:6.14.0:6.14.0
+grpc-google-cloud-spanner-v1:6.14.0:6.14.0
+grpc-google-cloud-spanner-admin-instance-v1:6.14.0:6.14.0
+grpc-google-cloud-spanner-admin-database-v1:6.14.0:6.14.0
+google-cloud-spanner:6.14.0:6.14.0
\ No newline at end of file