diff --git a/README.md b/README.md
index 74d113f00dd..2ed6ff04b4c 100644
--- a/README.md
+++ b/README.md
@@ -515,6 +515,9 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
| Add Numeric Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddNumericColumnSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/AddNumericColumnSample.java) |
| Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterSequenceSample.java) |
| Alter Table With Foreign Key Delete Cascade Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSample.java) |
+| Copy Backup Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CopyBackupSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CopyBackupSample.java) |
+| Create Backup With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateBackupWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateBackupWithEncryptionKey.java) |
+| Create Database With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithEncryptionKey.java) |
| Create Database With Version Retention Period Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSample.java) |
| Create Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceConfigSample.java) |
| Create Instance Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java) |
@@ -537,6 +540,9 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
| Pg Create Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCreateSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCreateSequenceSample.java) |
| Pg Drop Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgDropSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/PgDropSequenceSample.java) |
| Pg Interleaved Table Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgInterleavedTableSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/PgInterleavedTableSample.java) |
+| Pg Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgSpannerSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/PgSpannerSample.java) |
+| Restore Backup With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/RestoreBackupWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/RestoreBackupWithEncryptionKey.java) |
+| Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/SpannerSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/SpannerSample.java) |
| Update Database Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseSample.java) |
| Update Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSample.java) |
| Update Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateInstanceConfigSample.java) |
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 0ad21eebfc3..1d990d82e25 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -147,8 +147,8 @@
java-client-mr-integration-test
nam11
us-east1
- cmek-test-key-ring
- cmek-test-key
+ java-client-integration-test-cmek-ring
+ java-client-integration-test-cmek-key
mysample
quick-db
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 82ce69cd8e2..d14be6eabd8 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -146,8 +146,8 @@
java-client-mr-integration-test
nam11
us-east1
- cmek-test-key-ring
- cmek-test-key
+ java-client-integration-test-cmek-ring
+ java-client-integration-test-cmek-key
mysample
mysample-instance
quick-db
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 5dbe22f146e..e8d2155b33b 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -182,8 +182,8 @@
java-client-mr-integration-test
nam11
us-east1
- cmek-test-key-ring
- cmek-test-key
+ java-client-integration-test-cmek-ring
+ java-client-integration-test-cmek-key
mysample
quick-db
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CopyBackupSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CopyBackupSample.java
new file mode 100644
index 00000000000..ebac469ca7a
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CopyBackupSample.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.spanner.admin.generated;
+
+// [START spanner_copy_backup]
+
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.spanner.admin.database.v1.Backup;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+public class CopyBackupSample {
+
+ static void copyBackup() throws IOException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String sourceBackupId = "my-backup";
+ String destinationBackupId = "my-destination-backup";
+
+ try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+ copyBackup(databaseAdminClient, projectId, instanceId, sourceBackupId, destinationBackupId);
+ }
+ }
+
+ static void copyBackup(
+ DatabaseAdminClient databaseAdminClient,
+ String projectId,
+ String instanceId,
+ String sourceBackupId,
+ String destinationBackupId) {
+
+ Timestamp expireTime =
+ Timestamp.ofTimeMicroseconds(
+ TimeUnit.MICROSECONDS.convert(
+ System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14),
+ TimeUnit.MILLISECONDS));
+
+ // Initiate the request which returns an OperationFuture.
+ System.out.println("Copying backup [" + destinationBackupId + "]...");
+ Backup destinationBackup;
+ try {
+ // Creates a copy of an existing backup.
+ // Wait for the backup operation to complete.
+ destinationBackup = databaseAdminClient.copyBackupAsync(
+ InstanceName.of(projectId, instanceId), destinationBackupId,
+ BackupName.of(projectId, instanceId, sourceBackupId), expireTime.toProto()).get();
+ System.out.println("Copied backup [" + destinationBackup.getName() + "]");
+ } catch (ExecutionException e) {
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ // Load the metadata of the new backup from the server.
+ destinationBackup = databaseAdminClient.getBackup(destinationBackup.getName());
+ System.out.println(
+ String.format(
+ "Backup %s of size %d bytes was copied at %s for version of database at %s",
+ destinationBackup.getName(),
+ destinationBackup.getSizeBytes(),
+ OffsetDateTime.ofInstant(
+ Instant.ofEpochSecond(destinationBackup.getCreateTime().getSeconds(),
+ destinationBackup.getCreateTime().getNanos()),
+ ZoneId.systemDefault()),
+ OffsetDateTime.ofInstant(
+ Instant.ofEpochSecond(destinationBackup.getVersionTime().getSeconds(),
+ destinationBackup.getVersionTime().getNanos()),
+ ZoneId.systemDefault())));
+ }
+}
+// [END spanner_copy_backup]
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateBackupWithEncryptionKey.java
new file mode 100644
index 00000000000..39f070e639e
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateBackupWithEncryptionKey.java
@@ -0,0 +1,111 @@
+/*
+ * 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.admin.generated;
+
+// [START spanner_create_backup_with_encryption_key]
+
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.cloud.spanner.encryption.EncryptionConfigs;
+import com.google.protobuf.Timestamp;
+import com.google.spanner.admin.database.v1.Backup;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig;
+import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.EncryptionType;
+import com.google.spanner.admin.database.v1.CreateBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateBackupRequest;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.threeten.bp.LocalDateTime;
+import org.threeten.bp.OffsetDateTime;
+
+public class CreateBackupWithEncryptionKey {
+
+ static void createBackupWithEncryptionKey() throws IOException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+ String backupId = "my-backup";
+ String kmsKeyName =
+ "projects/" + projectId + "/locations//keyRings//cryptoKeys/";
+
+ try (DatabaseAdminClient adminClient = DatabaseAdminClient.create()) {
+ createBackupWithEncryptionKey(
+ adminClient,
+ projectId,
+ instanceId,
+ databaseId,
+ backupId,
+ kmsKeyName);
+ }
+ }
+
+ static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient,
+ String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName) {
+ // Set expire time to 14 days from now.
+ final Timestamp expireTime =
+ Timestamp.newBuilder().setSeconds(TimeUnit.MILLISECONDS.toSeconds((
+ System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14)))).build();
+ final BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+ Backup backup = Backup.newBuilder()
+ .setName(backupName.toString())
+ .setDatabase(DatabaseName.of(projectId, instanceId, databaseId).toString())
+ .setExpireTime(expireTime).build();
+
+ final CreateBackupRequest request =
+ CreateBackupRequest.newBuilder()
+ .setParent(InstanceName.of(projectId, instanceId).toString())
+ .setBackupId(backupId)
+ .setBackup(backup)
+ .setEncryptionConfig(
+ CreateBackupEncryptionConfig.newBuilder()
+ .setEncryptionType(EncryptionType.CUSTOMER_MANAGED_ENCRYPTION)
+ .setKmsKeyName(kmsKeyName).build()).build();
+ try {
+ System.out.println("Waiting for operation to complete...");
+ backup = adminClient.createBackupAsync(request).get(1200, TimeUnit.SECONDS);
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw SpannerExceptionFactory.asSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ } catch (TimeoutException e) {
+ // If the operation timed out propagates the timeout
+ throw SpannerExceptionFactory.propagateTimeout(e);
+ }
+ System.out.printf(
+ "Backup %s of size %d bytes was created at %s using encryption key %s%n",
+ backup.getName(),
+ backup.getSizeBytes(),
+ LocalDateTime.ofEpochSecond(
+ backup.getCreateTime().getSeconds(),
+ backup.getCreateTime().getNanos(),
+ OffsetDateTime.now().getOffset()),
+ kmsKeyName
+ );
+
+ return null;
+ }
+}
+// [END spanner_create_backup_with_encryption_key]
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithEncryptionKey.java
new file mode 100644
index 00000000000..2e9d9889f4c
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithEncryptionKey.java
@@ -0,0 +1,99 @@
+/*
+ * 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.admin.generated;
+
+// [START spanner_create_database_with_encryption_key]
+
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.common.collect.ImmutableList;
+import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
+import com.google.spanner.admin.database.v1.Database;
+import com.google.spanner.admin.database.v1.EncryptionConfig;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class CreateDatabaseWithEncryptionKey {
+
+ static void createDatabaseWithEncryptionKey() throws IOException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+ String kmsKeyName =
+ "projects/" + projectId + "/locations//keyRings//cryptoKeys/";
+
+ try (DatabaseAdminClient adminClient = DatabaseAdminClient.create()) {
+ createDatabaseWithEncryptionKey(
+ adminClient,
+ projectId,
+ instanceId,
+ databaseId,
+ kmsKeyName);
+ }
+ }
+
+ static void createDatabaseWithEncryptionKey(DatabaseAdminClient adminClient,
+ String projectId, String instanceId, String databaseId, String kmsKeyName) {
+ InstanceName instanceName = InstanceName.of(projectId, instanceId);
+ CreateDatabaseRequest request = CreateDatabaseRequest.newBuilder()
+ .setParent(instanceName.toString())
+ .setCreateStatement("CREATE DATABASE `" + databaseId + "`")
+ .setEncryptionConfig(EncryptionConfig.newBuilder().setKmsKeyName(kmsKeyName).build())
+ .addAllExtraStatements(
+ ImmutableList.of(
+ "CREATE TABLE Singers ("
+ + " SingerId INT64 NOT NULL,"
+ + " FirstName STRING(1024),"
+ + " LastName STRING(1024),"
+ + " SingerInfo BYTES(MAX)"
+ + ") PRIMARY KEY (SingerId)",
+ "CREATE TABLE Albums ("
+ + " SingerId INT64 NOT NULL,"
+ + " AlbumId INT64 NOT NULL,"
+ + " AlbumTitle STRING(MAX)"
+ + ") PRIMARY KEY (SingerId, AlbumId),"
+ + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE"
+ ))
+ .build();
+ try {
+ System.out.println("Waiting for operation to complete...");
+ Database createdDatabase =
+ adminClient.createDatabaseAsync(request).get(120, TimeUnit.SECONDS);
+
+ System.out.printf(
+ "Database %s created with encryption key %s%n",
+ createdDatabase.getName(),
+ createdDatabase.getEncryptionConfig().getKmsKeyName()
+ );
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw SpannerExceptionFactory.asSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ } catch (TimeoutException e) {
+ // If the operation timed out propagates the timeout
+ throw SpannerExceptionFactory.propagateTimeout(e);
+ }
+ }
+}
+// [END spanner_create_database_with_encryption_key]
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgSpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgSpannerSample.java
new file mode 100644
index 00000000000..b2e6eafb0f5
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgSpannerSample.java
@@ -0,0 +1,1626 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.spanner.admin.generated;
+
+import com.google.api.gax.paging.Page;
+import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.KeyRange;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.Options;
+import com.google.cloud.spanner.ReadOnlyTransaction;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerBatchUpdateException;
+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.Struct;
+import com.google.cloud.spanner.TimestampBound;
+import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListBackupOperationsPagedResponse;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseOperationsPagedResponse;
+import com.google.common.io.BaseEncoding;
+import com.google.longrunning.Operation;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.CopyBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
+import com.google.spanner.admin.database.v1.Database;
+import com.google.spanner.admin.database.v1.DatabaseDialect;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.ListBackupOperationsRequest;
+import com.google.spanner.admin.database.v1.ListDatabaseOperationsRequest;
+import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata;
+import com.google.spanner.admin.instance.v1.InstanceName;
+import com.google.spanner.v1.ExecuteSqlRequest;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Example code for using the Cloud Spanner PostgreSQL interface.
+ */
+public class PgSpannerSample {
+
+ // [START spanner_postgresql_insert_data]
+ static final List SINGERS =
+ Arrays.asList(
+ new Singer(1, "Marc", "Richards"),
+ new Singer(2, "Catalina", "Smith"),
+ new Singer(3, "Alice", "Trentor"),
+ new Singer(4, "Lea", "Martin"),
+ new Singer(5, "David", "Lomond"));
+ static final List ALBUMS =
+ Arrays.asList(
+ new Album(1, 1, "Total Junk"),
+ new Album(1, 2, "Go, Go, Go"),
+ new Album(2, 1, "Green"),
+ new Album(2, 2, "Forever Hold Your Peace"),
+ new Album(2, 3, "Terrified"));
+ // [END spanner_postgresql_insert_data]
+
+ /**
+ * Class to contain performance sample data.
+ */
+ static class Performance {
+
+ final long singerId;
+ final long venueId;
+ final String eventDate;
+ final long revenue;
+
+ Performance(long singerId, long venueId, String eventDate, long revenue) {
+ this.singerId = singerId;
+ this.venueId = venueId;
+ this.eventDate = eventDate;
+ this.revenue = revenue;
+ }
+ }
+
+ // [START spanner_postgresql_insert_data_with_timestamp_column]
+ static final List PERFORMANCES =
+ Arrays.asList(
+ new Performance(1, 4, "2017-10-05", 11000),
+ new Performance(1, 19, "2017-11-02", 15000),
+ new Performance(2, 42, "2017-12-23", 7000));
+ // [START spanner_postgresql_insert_datatypes_data]
+
+ static Value availableDates1 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-12-01"),
+ Date.parseDate("2020-12-02"),
+ Date.parseDate("2020-12-03")));
+ static Value availableDates2 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-11-01"),
+ Date.parseDate("2020-11-05"),
+ Date.parseDate("2020-11-15")));
+ static Value availableDates3 =
+ Value.dateArray(Arrays.asList(Date.parseDate("2020-10-01"), Date.parseDate("2020-10-07")));
+ // [END spanner_postgresql_insert_data_with_timestamp_column]
+ static String exampleBytes1 = BaseEncoding.base64().encode("Hello World 1".getBytes());
+ static String exampleBytes2 = BaseEncoding.base64().encode("Hello World 2".getBytes());
+ static String exampleBytes3 = BaseEncoding.base64().encode("Hello World 3".getBytes());
+ static final List VENUES =
+ Arrays.asList(
+ new Venue(
+ 4,
+ "Venue 4",
+ exampleBytes1,
+ 1800,
+ availableDates1,
+ "2018-09-02",
+ false,
+ 0.85543f,
+ new BigDecimal("215100.10")),
+ new Venue(
+ 19,
+ "Venue 19",
+ exampleBytes2,
+ 6300,
+ availableDates2,
+ "2019-01-15",
+ true,
+ 0.98716f,
+ new BigDecimal("1200100.00")),
+ new Venue(
+ 42,
+ "Venue 42",
+ exampleBytes3,
+ 3000,
+ availableDates3,
+ "2018-10-01",
+ false,
+ 0.72598f,
+ new BigDecimal("390650.99")));
+ // [END spanner_postgresql_insert_datatypes_data]
+
+ /**
+ * Class to contain venue sample data.
+ */
+ static class Venue {
+
+ final long venueId;
+ final String venueName;
+ final String venueInfo;
+ final long capacity;
+ final Value availableDates;
+ final String lastContactDate;
+ final boolean outdoorVenue;
+ final float popularityScore;
+ final BigDecimal revenue;
+
+ Venue(
+ long venueId,
+ String venueName,
+ String venueInfo,
+ long capacity,
+ Value availableDates,
+ String lastContactDate,
+ boolean outdoorVenue,
+ float popularityScore,
+ BigDecimal revenue) {
+ this.venueId = venueId;
+ this.venueName = venueName;
+ this.venueInfo = venueInfo;
+ this.capacity = capacity;
+ this.availableDates = availableDates;
+ this.lastContactDate = lastContactDate;
+ this.outdoorVenue = outdoorVenue;
+ this.popularityScore = popularityScore;
+ this.revenue = revenue;
+ }
+ }
+
+ // [START spanner_postgresql_create_database]
+ static void createPostgreSqlDatabase(
+ DatabaseAdminClient dbAdminClient, String projectId, String instanceId, String databaseId) {
+ final CreateDatabaseRequest request =
+ CreateDatabaseRequest.newBuilder()
+ .setCreateStatement("CREATE DATABASE \"" + databaseId + "\"")
+ .setParent(InstanceName.of(projectId, instanceId).toString())
+ .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build();
+
+ try {
+ // Initiate the request which returns an OperationFuture.
+ Database db = dbAdminClient.createDatabaseAsync(request).get();
+ System.out.println("Created database [" + db.getName() + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_database]
+
+ // [START spanner_postgresql_insert_data]
+ static void writeExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Singer singer : SINGERS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Singers")
+ .set("SingerId")
+ .to(singer.singerId)
+ .set("FirstName")
+ .to(singer.firstName)
+ .set("LastName")
+ .to(singer.lastName)
+ .build());
+ }
+ for (Album album : ALBUMS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Albums")
+ .set("SingerId")
+ .to(album.singerId)
+ .set("AlbumId")
+ .to(album.albumId)
+ .set("AlbumTitle")
+ .to(album.albumTitle)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_data]
+
+ // [START spanner_postgresql_delete_data]
+ static void deleteExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+
+ // KeySet.Builder can be used to delete a specific set of rows.
+ // Delete the Albums with the key values (2,1) and (2,3).
+ mutations.add(
+ Mutation.delete(
+ "Albums", KeySet.newBuilder().addKey(Key.of(2, 1)).addKey(Key.of(2, 3)).build()));
+
+ // KeyRange can be used to delete rows with a key in a specific range.
+ // Delete a range of rows where the column key is >=3 and <5
+ mutations.add(
+ Mutation.delete("Singers", KeySet.range(KeyRange.closedOpen(Key.of(3), Key.of(5)))));
+
+ // KeySet.all() can be used to delete all the rows in a table.
+ // Delete remaining Singers rows, which will also delete the remaining Albums rows since it was
+ // defined with ON DELETE CASCADE.
+ mutations.add(Mutation.delete("Singers", KeySet.all()));
+
+ dbClient.write(mutations);
+ System.out.printf("Records deleted.\n");
+ }
+ // [END spanner_postgresql_delete_data]
+
+ // [START spanner_postgresql_query_data]
+ static void query(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse() // Execute a single read or query against Cloud Spanner.
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1),
+ resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data]
+
+ // [START spanner_postgresql_read_data]
+ static void read(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .read(
+ "Albums",
+ KeySet.all(), // Read all rows in a table.
+ Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1),
+ resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data]
+
+ // [START spanner_postgresql_add_column]
+ static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint")).get();
+ System.out.println("Added MarketingBudget column");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_add_column]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64".
+ // [START spanner_postgresql_update_data]
+ static void update(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(100000)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(500000)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_update_data]
+
+ // [START spanner_postgresql_read_write_transaction]
+ static void writeWithTransaction(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ Struct row =
+ transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
+ long album2Budget = row.getLong(0);
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ long album1Budget =
+ transaction
+ .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
+ .getLong(0);
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(album1Budget)
+ .build());
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(album2Budget)
+ .build());
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_read_write_transaction]
+
+ // [START spanner_postgresql_query_data_with_new_column]
+ static void queryMarketingBudget(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
+ + "albumid as \"AlbumId\", marketingbudget as \"MarketingBudget\" "
+ + "FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" :
+ resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data_with_new_column]
+
+ // [START spanner_postgresql_create_index]
+ static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)")).get();
+ System.out.println("Added AlbumsByAlbumTitle index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_index]
+
+ // [START spanner_postgresql_read_data_with_index]
+ static void readUsingIndex(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data_with_index]
+
+ // [START spanner_postgresql_create_storing_index]
+ static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
+ + "INCLUDE (MarketingBudget)")).get();
+ System.out.println("Added AlbumsByAlbumTitle2 index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_storing_index]
+
+ // Before running this example, create a storing index AlbumsByAlbumTitle2 by applying the DDL
+ // statement "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)".
+ // [START spanner_postgresql_read_data_with_storing_index]
+ static void readStoringIndex(DatabaseClient dbClient) {
+ // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle2",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong(0),
+ resultSet.getString(1),
+ resultSet.isNull("marketingbudget") ? "NULL" : resultSet.getLong(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data_with_storing_index]
+
+ // [START spanner_postgresql_read_only_transaction]
+ static void readOnlyTransaction(DatabaseClient dbClient) {
+ // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
+ // We use a try-with-resource block to automatically do so.
+ try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
+ ResultSet queryResultSet =
+ transaction.executeQuery(
+ Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
+ while (queryResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ queryResultSet.getLong(0), queryResultSet.getLong(1),
+ queryResultSet.getString(2));
+ }
+ try (ResultSet readResultSet =
+ transaction.read(
+ "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (readResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ readResultSet.getLong(0), readResultSet.getLong(1),
+ readResultSet.getString(2));
+ }
+ }
+ }
+ }
+ // [END spanner_postgresql_read_only_transaction]
+
+ // [START spanner_postgresql_query_singers_table]
+ static void querySingersTable(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
+ + "firstname as \"FirstName\", lastname as \"LastName\" FROM Singers"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_singers_table]
+
+
+ // [START spanner_postgresql_dml_getting_started_insert]
+ static void writeUsingDml(DatabaseClient dbClient) {
+ // Insert 4 singer records
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
+ + "(12, 'Melissa', 'Garcia'), "
+ + "(13, 'Russell', 'Morales'), "
+ + "(14, 'Jacqueline', 'Long'), "
+ + "(15, 'Dylan', 'Shaw')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d records inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_getting_started_insert]
+
+ // [START spanner_postgresql_query_with_parameter]
+ static void queryWithParameter(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT singerid AS \"SingerId\", "
+ + "firstname as \"FirstName\", lastname as \"LastName\" "
+ + "FROM Singers "
+ + "WHERE LastName = $1")
+ .bind("p1")
+ .to("Garcia")
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_parameter]
+
+ // [START spanner_postgresql_dml_getting_started_update]
+ static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ String sql1 =
+ "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
+ + "SingerId = 2 and AlbumId = 2";
+ ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
+ long album2Budget = 0;
+ while (resultSet.next()) {
+ album2Budget = resultSet.getLong("MarketingBudget");
+ }
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ String sql2 =
+ "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
+ + "SingerId = 1 and AlbumId = 1";
+ ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
+ long album1Budget = 0;
+ while (resultSet2.next()) {
+ album1Budget = resultSet2.getLong("MarketingBudget");
+ }
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ Statement updateStatement =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1"
+ + "WHERE SingerId = 1 and AlbumId = 1")
+ .bind("p1")
+ .to(album1Budget)
+ .build();
+ transaction.executeUpdate(updateStatement);
+ Statement updateStatement2 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1"
+ + "WHERE SingerId = 2 and AlbumId = 2")
+ .bind("p1")
+ .to(album2Budget)
+ .build();
+ transaction.executeUpdate(updateStatement2);
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_getting_started_update]
+
+ // [START spanner_postgresql_create_table_using_ddl]
+ // [START spanner_postgresql_create_database]
+ static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ dbAdminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "CREATE TABLE Singers ("
+ + " SingerId bigint NOT NULL,"
+ + " FirstName character varying(1024),"
+ + " LastName character varying(1024),"
+ + " SingerInfo bytea,"
+ + " FullName character varying(2048) GENERATED "
+ + " ALWAYS AS (FirstName || ' ' || LastName) STORED,"
+ + " PRIMARY KEY (SingerId)"
+ + ")",
+ "CREATE TABLE Albums ("
+ + " SingerId bigint NOT NULL,"
+ + " AlbumId bigint NOT NULL,"
+ + " AlbumTitle character varying(1024),"
+ + " PRIMARY KEY (SingerId, AlbumId)"
+ + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get();
+ System.out.println("Created Singers & Albums tables in database: [" + databaseName + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw SpannerExceptionFactory.asSpannerException(e);
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_database]
+ // [END spanner_postgresql_create_table_using_ddl]
+
+ // [START spanner_postgresql_read_stale_data]
+ static void readStaleData(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse(TimestampBound.ofExactStaleness(15, TimeUnit.SECONDS))
+ .read(
+ "Albums", KeySet.all(),
+ Arrays.asList("SingerId", "AlbumId", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong(0),
+ resultSet.getLong(1),
+ resultSet.isNull(2) ? "NULL" : resultSet.getLong(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_stale_data]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT".
+ // In addition this update expects the LastUpdateTime column added by applying the DDL statement
+ // "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMPTZ"
+ // [START spanner_postgresql_update_data_with_timestamp_column]
+ static void updateWithTimestamp(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(1000000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(750000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_update_data_with_timestamp_column]
+
+ // [START spanner_postgresql_add_timestamp_column]
+ static void addLastUpdateTimestampColumn(
+ DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "ALTER TABLE Albums ADD COLUMN LastUpdateTime spanner.commit_timestamp")).get();
+ System.out.println("Added LastUpdateTime as a timestamp column in Albums table.");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_add_timestamp_column]
+
+ // [START spanner_postgresql_query_data_with_timestamp_column]
+ static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT singerid as \"SingerId\", albumid as \"AlbumId\", "
+ + "marketingbudget as \"MarketingBudget\","
+ + "lastupdatetime as \"LastUpdateTime\" FROM Albums"
+ + " ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"),
+ resultSet.isNull("LastUpdateTime") ? "NULL" : resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data_with_timestamp_column]
+
+ // [START spanner_postgresql_create_table_with_timestamp_column]
+ static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient,
+ DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ dbAdminClient.updateDatabaseDdlAsync(databaseName,
+ Arrays.asList(
+ "CREATE TABLE Performances ("
+ + " SingerId BIGINT NOT NULL,"
+ + " VenueId BIGINT NOT NULL,"
+ + " Revenue BIGINT,"
+ + " LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL,"
+ + " PRIMARY KEY (SingerId, VenueId))"
+ + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get();
+ System.out.println("Created Performances table in database: [" + databaseName + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_table_with_timestamp_column]
+
+ // [START spanner_postgresql_insert_data_with_timestamp_column]
+ static void writeExampleDataWithTimestamp(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Performance performance : PERFORMANCES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Performances")
+ .set("SingerId")
+ .to(performance.singerId)
+ .set("VenueId")
+ .to(performance.venueId)
+ .set("Revenue")
+ .to(performance.revenue)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_data_with_timestamp_column]
+
+ static void queryPerformancesTable(DatabaseClient dbClient) {
+ // Rows without an explicit value for Revenue will have a Revenue equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT singerid as \"SingerId\", venueid as \"VenueId\", "
+ + "revenue as \"Revenue\", lastupdatetime as \"LastUpdateTime\" "
+ + "FROM Performances ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("VenueId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("Revenue") ? "NULL" : resultSet.getLong("Revenue"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+
+ // [START spanner_postgresql_dml_standard_insert]
+ static void insertUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (10, 'Virginia', 'Watson')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_insert]
+
+ // [START spanner_postgresql_dml_standard_update]
+ static void updateUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 1";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record updated.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_update]
+
+ // [START spanner_postgresql_dml_standard_delete]
+ static void deleteUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql = "DELETE FROM Singers WHERE FirstName = 'Alice'";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record deleted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_delete]
+
+ // [START spanner_postgresql_dml_write_then_read]
+ static void writeAndReadUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Insert record.
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (11, 'Timothy', 'Campbell')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ // Read newly inserted record.
+ sql = "SELECT firstname as \"FirstName\", lastname as \"LastName\" FROM Singers WHERE "
+ + "SingerId = 11";
+ // We use a try-with-resource block to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet = transaction.executeQuery(Statement.of(sql))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s\n",
+ resultSet.getString("FirstName"), resultSet.getString("LastName"));
+ }
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_write_then_read]
+
+ // [START spanner_postgresql_dml_partitioned_update]
+ static void updateUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records updated.\n", rowCount);
+ }
+ // [END spanner_postgresql_dml_partitioned_update]
+
+ // [START spanner_postgresql_dml_partitioned_delete]
+ static void deleteUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "DELETE FROM Singers WHERE SingerId > 10";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records deleted.\n", rowCount);
+ }
+ // [END spanner_postgresql_dml_partitioned_delete]
+
+ // [START spanner_postgresql_dml_batch_update]
+ static void updateUsingBatchDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ List stmts = new ArrayList();
+ String sql =
+ "INSERT INTO Albums "
+ + "(SingerId, AlbumId, AlbumTitle, MarketingBudget) "
+ + "VALUES (1, 3, 'Test Album Title', 10000) ";
+ stmts.add(Statement.of(sql));
+ sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 3";
+ stmts.add(Statement.of(sql));
+ long[] rowCounts;
+ try {
+ rowCounts = transaction.batchUpdate(stmts);
+ } catch (SpannerBatchUpdateException e) {
+ rowCounts = e.getUpdateCounts();
+ }
+ for (int i = 0; i < rowCounts.length; i++) {
+ System.out.printf("%d record updated by stmt %d.\n", rowCounts[i], i);
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_batch_update]
+
+ // [START spanner_postgresql_create_table_with_datatypes]
+ static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient,
+ DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ dbAdminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "CREATE TABLE Venues ("
+ + " VenueId BIGINT NOT NULL,"
+ + " VenueName character varying(100),"
+ + " VenueInfo bytea,"
+ + " Capacity BIGINT,"
+ + " OutdoorVenue BOOL, "
+ + " PopularityScore FLOAT8, "
+ + " Revenue NUMERIC, "
+ + " LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL,"
+ + " PRIMARY KEY (VenueId))")).get();
+ System.out.println("Created Venues table in database: [" + databaseName + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_table_with_datatypes]
+
+ // [START spanner_postgresql_insert_datatypes_data]
+ static void writeDatatypesData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Venue venue : VENUES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Venues")
+ .set("VenueId")
+ .to(venue.venueId)
+ .set("VenueName")
+ .to(venue.venueName)
+ .set("VenueInfo")
+ .to(venue.venueInfo)
+ .set("Capacity")
+ .to(venue.capacity)
+ .set("OutdoorVenue")
+ .to(venue.outdoorVenue)
+ .set("PopularityScore")
+ .to(venue.popularityScore)
+ .set("Revenue")
+ .to(venue.revenue)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_datatypes_data]
+
+ // [START spanner_postgresql_query_with_bool_parameter]
+ static void queryWithBool(DatabaseClient dbClient) {
+ boolean exampleBool = true;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\","
+ + " outdoorvenue as \"OutdoorVenue\" FROM Venues "
+ + "WHERE OutdoorVenue = $1")
+ .bind("p1")
+ .to(exampleBool)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %b\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getBoolean("OutdoorVenue"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_bool_parameter]
+
+ // [START spanner_postgresql_query_with_bytes_parameter]
+ static void queryWithBytes(DatabaseClient dbClient) {
+ ByteArray exampleBytes =
+ ByteArray.fromBase64(BaseEncoding.base64().encode("Hello World 1".getBytes()));
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\" FROM Venues "
+ + "WHERE VenueInfo = $1")
+ .bind("p1")
+ .to(exampleBytes)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_bytes_parameter]
+
+ // [START spanner_postgresql_query_with_float_parameter]
+ static void queryWithFloat(DatabaseClient dbClient) {
+ float exampleFloat = 0.8f;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "popularityscore as \"PopularityScore\" FROM Venues "
+ + "WHERE PopularityScore > $1")
+ .bind("p1")
+ .to(exampleFloat)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %f\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getDouble("PopularityScore"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_float_parameter]
+
+ // [START spanner_postgresql_query_with_int_parameter]
+ static void queryWithInt(DatabaseClient dbClient) {
+ long exampleInt = 3000;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "capacity as \"Capacity\" "
+ + "FROM Venues " + "WHERE Capacity >= $1")
+ .bind("p1")
+ .to(exampleInt)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %d\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getLong("Capacity"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_int_parameter]
+
+ // [START spanner_postgresql_query_with_string_parameter]
+ static void queryWithString(DatabaseClient dbClient) {
+ String exampleString = "Venue 42";
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\" FROM Venues WHERE"
+ + " VenueName = $1")
+ .bind("p1")
+ .to(exampleString)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_string_parameter]
+
+ // [START spanner_postgresql_query_with_timestamp_parameter]
+ static void queryWithTimestampParameter(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "lastupdatetime as \"LastUpdateTime\" FROM Venues "
+ + "WHERE LastUpdateTime < $1")
+ .bind("p1")
+ .to(Timestamp.now())
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_timestamp_parameter]
+
+ // [START spanner_postgresql_query_with_numeric_parameter]
+ static void queryWithNumeric(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "revenue as \"Revenue\" FROM Venues\n"
+ + "WHERE Revenue >= $1")
+ .bind("p1")
+ .to(Value.pgNumeric("300000"))
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s%n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getValue("Revenue"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_numeric_parameter]
+
+ // [START spanner_postgresql_create_client_with_query_options]
+ static void clientWithQueryOptions(DatabaseId db) {
+ SpannerOptions options =
+ SpannerOptions.newBuilder()
+ .setDefaultQueryOptions(
+ db, ExecuteSqlRequest.QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying the
+ // "INFORMATION_SCHEMA.spanner_postgresql_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build();
+ Spanner spanner = options.getService();
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_create_client_with_query_options]
+
+ // [START spanner_postgresql_query_with_query_options]
+ static void queryWithQueryOptions(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement
+ .newBuilder("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")
+ .withQueryOptions(ExecuteSqlRequest.QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying
+ // the "INFORMATION_SCHEMA.spanner_postgresql_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build())) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_query_options]
+
+ // [START spanner_postgresql_list_backup_operations]
+ static void listBackupOperations(
+ DatabaseAdminClient databaseAdminClient,
+ String projectId, String instanceId,
+ String databaseId, String backupId) {
+ com.google.spanner.admin.database.v1.InstanceName instanceName =
+ com.google.spanner.admin.database.v1.InstanceName.of(projectId, instanceId);
+ // Get 'CreateBackup' operations for the sample database.
+ String filter =
+ String.format(
+ "(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.CreateBackupMetadata) "
+ + "AND (metadata.database:%s)",
+ DatabaseName.of(projectId, instanceId, databaseId).toString());
+ ListBackupOperationsRequest listBackupOperationsRequest =
+ ListBackupOperationsRequest.newBuilder()
+ .setParent(instanceName.toString()).setFilter(filter).build();
+ ListBackupOperationsPagedResponse createBackupOperations
+ = databaseAdminClient.listBackupOperations(listBackupOperationsRequest);
+ System.out.println("Create Backup Operations:");
+ for (Operation op : createBackupOperations.iterateAll()) {
+ try {
+ CreateBackupMetadata metadata = op.getMetadata().unpack(CreateBackupMetadata.class);
+ System.out.println(
+ String.format(
+ "Backup %s on database %s pending: %d%% complete",
+ metadata.getName(),
+ metadata.getDatabase(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain CreateBackupMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ // Get copy backup operations for the sample database.
+ filter = String.format(
+ "(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.CopyBackupMetadata) "
+ + "AND (metadata.source_backup:%s)",
+ BackupName.of(projectId, instanceId, backupId).toString());
+ listBackupOperationsRequest =
+ ListBackupOperationsRequest.newBuilder()
+ .setParent(instanceName.toString()).setFilter(filter).build();
+ ListBackupOperationsPagedResponse copyBackupOperations =
+ databaseAdminClient.listBackupOperations(listBackupOperationsRequest);
+ System.out.println("Copy Backup Operations:");
+ for (Operation op : copyBackupOperations.iterateAll()) {
+ try {
+ CopyBackupMetadata copyBackupMetadata =
+ op.getMetadata().unpack(CopyBackupMetadata.class);
+ System.out.println(
+ String.format(
+ "Copy Backup %s on backup %s pending: %d%% complete",
+ copyBackupMetadata.getName(),
+ copyBackupMetadata.getSourceBackup(),
+ copyBackupMetadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain CopyBackupMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_postgresql_list_backup_operations]
+
+ // [START spanner_postgresql_list_database_operations]
+ static void listDatabaseOperations(
+ DatabaseAdminClient dbAdminClient, String projectId, String instanceId) {
+ // Get optimize restored database operations.
+ com.google.cloud.Timestamp last24Hours = com.google.cloud.Timestamp.ofTimeSecondsAndNanos(
+ TimeUnit.SECONDS.convert(
+ TimeUnit.HOURS.convert(com.google.cloud.Timestamp.now().getSeconds(), TimeUnit.SECONDS)
+ - 24,
+ TimeUnit.HOURS), 0);
+ String filter = String.format("(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata) AND "
+ + "(metadata.progress.start_time > \"%s\")", last24Hours);
+ ListDatabaseOperationsRequest listDatabaseOperationsRequest =
+ ListDatabaseOperationsRequest.newBuilder()
+ .setParent(com.google.spanner.admin.instance.v1.InstanceName.of(
+ projectId, instanceId).toString()).setFilter(filter).build();
+ ListDatabaseOperationsPagedResponse pagedResponse
+ = dbAdminClient.listDatabaseOperations(listDatabaseOperationsRequest);
+ for (Operation op : pagedResponse.iterateAll()) {
+ try {
+ OptimizeRestoredDatabaseMetadata metadata =
+ op.getMetadata().unpack(OptimizeRestoredDatabaseMetadata.class);
+ System.out.println(String.format(
+ "Database %s restored from backup is %d%% optimized",
+ metadata.getName(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain OptimizeRestoredDatabaseMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_postgresql_list_database_operations]
+
+ static void run(
+ DatabaseClient dbClient,
+ DatabaseAdminClient dbAdminClient,
+ String command,
+ DatabaseId database,
+ String backupId) {
+ DatabaseName databaseName = DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase());
+ switch (command) {
+ case "createpgdatabase":
+ createPostgreSqlDatabase(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase());
+ break;
+ case "write":
+ writeExampleData(dbClient);
+ break;
+ case "delete":
+ deleteExampleData(dbClient);
+ break;
+ case "query":
+ query(dbClient);
+ break;
+ case "read":
+ read(dbClient);
+ break;
+ case "addmarketingbudget":
+ addMarketingBudget(dbAdminClient, databaseName);
+ break;
+ case "update":
+ update(dbClient);
+ break;
+ case "writetransaction":
+ writeWithTransaction(dbClient);
+ break;
+ case "querymarketingbudget":
+ queryMarketingBudget(dbClient);
+ break;
+ case "addindex":
+ addIndex(dbAdminClient, databaseName);
+ break;
+ case "readindex":
+ readUsingIndex(dbClient);
+ break;
+ case "addstoringindex":
+ addStoringIndex(dbAdminClient, databaseName);
+ break;
+ case "readstoringindex":
+ readStoringIndex(dbClient);
+ break;
+ case "readonlytransaction":
+ readOnlyTransaction(dbClient);
+ break;
+ case "querysingerstable":
+ querySingersTable(dbClient);
+ break;
+ case "writeusingdml":
+ writeUsingDml(dbClient);
+ break;
+ case "querywithparameter":
+ queryWithParameter(dbClient);
+ break;
+ case "writewithtransactionusingdml":
+ writeWithTransactionUsingDml(dbClient);
+ break;
+ case "createtableusingddl":
+ createTableUsingDdl(dbAdminClient, databaseName);
+ break;
+ case "readstaledata":
+ readStaleData(dbClient);
+ break;
+ case "addlastupdatetimestampcolumn":
+ addLastUpdateTimestampColumn(dbAdminClient, databaseName);
+ break;
+ case "updatewithtimestamp":
+ updateWithTimestamp(dbClient);
+ break;
+ case "querywithtimestamp":
+ queryMarketingBudgetWithTimestamp(dbClient);
+ break;
+ case "createtablewithtimestamp":
+ createTableWithTimestamp(dbAdminClient, databaseName);
+ break;
+ case "writewithtimestamp":
+ writeExampleDataWithTimestamp(dbClient);
+ break;
+ case "queryperformancestable":
+ queryPerformancesTable(dbClient);
+ break;
+ case "insertusingdml":
+ insertUsingDml(dbClient);
+ break;
+ case "updateusingdml":
+ updateUsingDml(dbClient);
+ break;
+ case "deleteusingdml":
+ deleteUsingDml(dbClient);
+ break;
+ case "writeandreadusingdml":
+ writeAndReadUsingDml(dbClient);
+ break;
+ case "updateusingpartitioneddml":
+ updateUsingPartitionedDml(dbClient);
+ break;
+ case "deleteusingpartitioneddml":
+ deleteUsingPartitionedDml(dbClient);
+ break;
+ case "updateusingbatchdml":
+ updateUsingBatchDml(dbClient);
+ break;
+ case "createtablewithdatatypes":
+ createTableWithDatatypes(dbAdminClient, databaseName);
+ break;
+ case "writedatatypesdata":
+ writeDatatypesData(dbClient);
+ break;
+ case "querywithbool":
+ queryWithBool(dbClient);
+ break;
+ case "querywithbytes":
+ queryWithBytes(dbClient);
+ break;
+ case "querywithfloat":
+ queryWithFloat(dbClient);
+ break;
+ case "querywithint":
+ queryWithInt(dbClient);
+ break;
+ case "querywithstring":
+ queryWithString(dbClient);
+ break;
+ case "querywithtimestampparameter":
+ queryWithTimestampParameter(dbClient);
+ break;
+ case "querywithnumeric":
+ queryWithNumeric(dbClient);
+ break;
+ case "clientwithqueryoptions":
+ clientWithQueryOptions(database);
+ break;
+ case "querywithqueryoptions":
+ queryWithQueryOptions(dbClient);
+ break;
+ case "listbackupoperations":
+ listBackupOperations(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase(), backupId);
+ break;
+ case "listdatabaseoperations":
+ listDatabaseOperations(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance());
+ break;
+ default:
+ printUsageAndExit();
+ }
+ }
+
+ static void printUsageAndExit() {
+ System.err.println("Usage:");
+ System.err.println(" PgSpannerExample ");
+ System.err.println();
+ System.err.println("Examples:");
+ System.err.println(" PgSpannerExample createdatabase my-instance example-db");
+ System.err.println(" PgSpannerExample write my-instance example-db");
+ System.err.println(" PgSpannerExample delete my-instance example-db");
+ System.err.println(" PgSpannerExample query my-instance example-db");
+ System.err.println(" PgSpannerExample read my-instance example-db");
+ System.err.println(" PgSpannerExample addmarketingbudget my-instance example-db");
+ System.err.println(" PgSpannerExample update my-instance example-db");
+ System.err.println(" PgSpannerExample writetransaction my-instance example-db");
+ System.err.println(" PgSpannerExample querymarketingbudget my-instance example-db");
+ System.err.println(" PgSpannerExample addindex my-instance example-db");
+ System.err.println(" PgSpannerExample readindex my-instance example-db");
+ System.err.println(" PgSpannerExample addstoringindex my-instance example-db");
+ System.err.println(" PgSpannerExample readstoringindex my-instance example-db");
+ System.err.println(" PgSpannerExample readonlytransaction my-instance example-db");
+ System.err.println(" PgSpannerExample querysingerstable my-instance example-db");
+ System.err.println(" PgSpannerExample writeusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample querywithparameter my-instance example-db");
+ System.err.println(" PgSpannerExample writewithtransactionusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample createtableforsamples my-instance example-db");
+ System.err.println(" PgSpannerExample writewithtimestamp my-instance example-db");
+ System.err.println(" PgSpannerExample queryperformancestable my-instance example-db");
+ System.err.println(" PgSpannerExample writestructdata my-instance example-db");
+ System.err.println(" PgSpannerExample insertusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample updateusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample deleteusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample writeandreadusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample writeusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample deleteusingpartitioneddml my-instance example-db");
+ System.err.println(" PgSpannerExample updateusingbatchdml my-instance example-db");
+ System.err.println(" PgSpannerExample createtablewithdatatypes my-instance example-db");
+ System.err.println(" PgSpannerExample writedatatypesdata my-instance example-db");
+ System.err.println(" PgSpannerExample querywithbool my-instance example-db");
+ System.err.println(" PgSpannerExample querywithbytes my-instance example-db");
+ System.err.println(" PgSpannerExample querywithfloat my-instance example-db");
+ System.err.println(" PgSpannerExample querywithint my-instance example-db");
+ System.err.println(" PgSpannerExample querywithstring my-instance example-db");
+ System.err.println(" PgSpannerExample querywithtimestampparameter my-instance example-db");
+ System.err.println(" PgSpannerExample clientwithqueryoptions my-instance example-db");
+ System.err.println(" PgSpannerExample querywithqueryoptions my-instance example-db");
+ System.err.println(" PgSpannerExample listbackupoperations my-instance example-db");
+ System.err.println(" PgSpannerExample listdatabaseoperations my-instance example-db");
+ System.exit(1);
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 3) {
+ printUsageAndExit();
+ }
+ // [START spanner_init_client]
+ SpannerOptions options = SpannerOptions.newBuilder().build();
+ Spanner spanner = options.getService();
+ DatabaseAdminClient dbAdminClient = null;
+ try {
+ // [END spanner_init_client]
+ final String command = args[0];
+ DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]);
+
+ // This will return the default project id based on the environment.
+ String clientProject = spanner.getOptions().getProjectId();
+ if (!db.getInstanceId().getProject().equals(clientProject)) {
+ System.err.println(
+ "Invalid project specified. Project in the database id should match the"
+ + "project name set in the environment variable GOOGLE_CLOUD_PROJECT. Expected: "
+ + clientProject);
+ printUsageAndExit();
+ }
+ // Generate a backup id for the sample database.
+ String backupId = null;
+ if (args.length == 4) {
+ backupId = args[3];
+ }
+
+ // [START spanner_init_client]
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ dbAdminClient = DatabaseAdminClient.create();
+ // [END spanner_init_client]
+
+ // Use client here...
+ run(dbClient, dbAdminClient, command, db, backupId);
+ // [START spanner_init_client]
+ } finally {
+ if (dbAdminClient != null) {
+ if (!dbAdminClient.isShutdown() || !dbAdminClient.isTerminated()) {
+ dbAdminClient.close();
+ }
+ }
+ spanner.close();
+ }
+ // [END spanner_init_client]
+ System.out.println("Closed client");
+ }
+
+ /**
+ * Class to contain singer sample data.
+ */
+ static class Singer {
+
+ final long singerId;
+ final String firstName;
+ final String lastName;
+
+ Singer(long singerId, String firstName, String lastName) {
+ this.singerId = singerId;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+ }
+
+ /**
+ * Class to contain album sample data.
+ */
+ static class Album {
+
+ final long singerId;
+ final long albumId;
+ final String albumTitle;
+
+ Album(long singerId, long albumId, String albumTitle) {
+ this.singerId = singerId;
+ this.albumId = albumId;
+ this.albumTitle = albumTitle;
+ }
+ }
+}
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/RestoreBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/RestoreBackupWithEncryptionKey.java
new file mode 100644
index 00000000000..9c2ef5b3a75
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/RestoreBackupWithEncryptionKey.java
@@ -0,0 +1,88 @@
+/*
+ * 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.admin.generated;
+
+// [START spanner_restore_backup_with_encryption_key]
+
+import static com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION;
+
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.Database;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig;
+import com.google.spanner.admin.database.v1.RestoreDatabaseRequest;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+public class RestoreBackupWithEncryptionKey {
+
+ static void restoreBackupWithEncryptionKey() throws IOException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+ String backupId = "my-backup";
+ String kmsKeyName =
+ "projects/" + projectId + "/locations//keyRings//cryptoKeys/";
+
+ try (DatabaseAdminClient adminClient = DatabaseAdminClient.create()) {
+ restoreBackupWithEncryptionKey(
+ adminClient,
+ projectId,
+ instanceId,
+ backupId,
+ databaseId,
+ kmsKeyName);
+ }
+ }
+
+ static Void restoreBackupWithEncryptionKey(DatabaseAdminClient adminClient,
+ String projectId, String instanceId, String backupId, String restoreId, String kmsKeyName) {
+ RestoreDatabaseRequest request =
+ RestoreDatabaseRequest.newBuilder()
+ .setParent(InstanceName.of(projectId, instanceId).toString())
+ .setDatabaseId(restoreId)
+ .setBackup(BackupName.of(projectId, instanceId, backupId).toString())
+ .setEncryptionConfig(RestoreDatabaseEncryptionConfig.newBuilder()
+ .setEncryptionType(CUSTOMER_MANAGED_ENCRYPTION).setKmsKeyName(kmsKeyName)).build();
+ Database database;
+ try {
+ System.out.println("Waiting for operation to complete...");
+ database = adminClient.restoreDatabaseAsync(request).get();;
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw SpannerExceptionFactory.asSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+
+ System.out.printf(
+ "Database %s restored to %s from backup %s using encryption key %s%n",
+ database.getRestoreInfo().getBackupInfo().getSourceDatabase(),
+ database.getName(),
+ database.getRestoreInfo().getBackupInfo().getBackup(),
+ database.getEncryptionConfig().getKmsKeyName()
+ );
+ return null;
+ }
+}
+// [END spanner_restore_backup_with_encryption_key]
diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/SpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/SpannerSample.java
new file mode 100644
index 00000000000..6e805dc1322
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/SpannerSample.java
@@ -0,0 +1,2275 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.spanner.admin.generated;
+
+import static com.google.cloud.spanner.Type.StructField;
+
+import com.google.api.gax.longrunning.OperationFuture;
+import com.google.api.gax.longrunning.OperationSnapshot;
+import com.google.api.gax.retrying.RetryingFuture;
+import com.google.api.gax.rpc.NotFoundException;
+import com.google.api.gax.rpc.StatusCode;
+import com.google.api.gax.rpc.StatusCode.Code;
+import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.KeyRange;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.ReadOnlyTransaction;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerBatchUpdateException;
+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.Struct;
+import com.google.cloud.spanner.TimestampBound;
+import com.google.cloud.spanner.Type;
+import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListBackupOperationsPagedResponse;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListBackupsPagedResponse;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseOperationsPagedResponse;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.io.BaseEncoding;
+import com.google.longrunning.Operation;
+import com.google.protobuf.FieldMask;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Timestamp;
+import com.google.spanner.admin.database.v1.Backup;
+import com.google.spanner.admin.database.v1.BackupInfo;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.CopyBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import com.google.spanner.admin.database.v1.ListBackupOperationsRequest;
+import com.google.spanner.admin.database.v1.ListBackupsRequest;
+import com.google.spanner.admin.database.v1.ListDatabaseOperationsRequest;
+import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata;
+import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
+import com.google.spanner.admin.database.v1.RestoreDatabaseRequest;
+import com.google.spanner.admin.database.v1.RestoreInfo;
+import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Example code for using the Cloud Spanner API. This example demonstrates all the common operations
+ * that can be done on Cloud Spanner. These are:
+ *
+ *
+ *
+ *
+ * - Creating a Cloud Spanner database.
+ *
- Writing, reading and executing SQL queries.
+ *
- Writing data using a read-write transaction.
+ *
- Using an index to read and execute SQL queries over data.
+ *
- Using commit timestamp for tracking when a record was last updated.
+ *
- Using Google API Extensions for Java to make thread-safe requests via long-running
+ * operations. http://googleapis.github.io/gax-java/
+ *
+ */
+public class SpannerSample {
+
+ /**
+ * Class to contain singer sample data.
+ */
+ static class Singer {
+
+ final long singerId;
+ final String firstName;
+ final String lastName;
+
+ Singer(long singerId, String firstName, String lastName) {
+ this.singerId = singerId;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+ }
+
+ /**
+ * Class to contain album sample data.
+ */
+ static class Album {
+
+ final long singerId;
+ final long albumId;
+ final String albumTitle;
+
+ Album(long singerId, long albumId, String albumTitle) {
+ this.singerId = singerId;
+ this.albumId = albumId;
+ this.albumTitle = albumTitle;
+ }
+ }
+
+ /**
+ * Class to contain performance sample data.
+ */
+ static class Performance {
+
+ final long singerId;
+ final long venueId;
+ final String eventDate;
+ final long revenue;
+
+ Performance(long singerId, long venueId, String eventDate, long revenue) {
+ this.singerId = singerId;
+ this.venueId = venueId;
+ this.eventDate = eventDate;
+ this.revenue = revenue;
+ }
+ }
+
+ /**
+ * Class to contain venue sample data.
+ */
+ static class Venue {
+
+ final long venueId;
+ final String venueName;
+ final String venueInfo;
+ final long capacity;
+ final Value availableDates;
+ final String lastContactDate;
+ final boolean outdoorVenue;
+ final float popularityScore;
+ final BigDecimal revenue;
+ final Value venueDetails;
+
+ Venue(
+ long venueId,
+ String venueName,
+ String venueInfo,
+ long capacity,
+ Value availableDates,
+ String lastContactDate,
+ boolean outdoorVenue,
+ float popularityScore,
+ BigDecimal revenue,
+ Value venueDetails) {
+ this.venueId = venueId;
+ this.venueName = venueName;
+ this.venueInfo = venueInfo;
+ this.capacity = capacity;
+ this.availableDates = availableDates;
+ this.lastContactDate = lastContactDate;
+ this.outdoorVenue = outdoorVenue;
+ this.popularityScore = popularityScore;
+ this.revenue = revenue;
+ this.venueDetails = venueDetails;
+ }
+ }
+
+ // [START spanner_insert_data]
+ static final List SINGERS =
+ Arrays.asList(
+ new Singer(1, "Marc", "Richards"),
+ new Singer(2, "Catalina", "Smith"),
+ new Singer(3, "Alice", "Trentor"),
+ new Singer(4, "Lea", "Martin"),
+ new Singer(5, "David", "Lomond"));
+
+ static final List ALBUMS =
+ Arrays.asList(
+ new Album(1, 1, "Total Junk"),
+ new Album(1, 2, "Go, Go, Go"),
+ new Album(2, 1, "Green"),
+ new Album(2, 2, "Forever Hold Your Peace"),
+ new Album(2, 3, "Terrified"));
+ // [END spanner_insert_data]
+
+ // [START spanner_insert_data_with_timestamp_column]
+ static final List PERFORMANCES =
+ Arrays.asList(
+ new Performance(1, 4, "2017-10-05", 11000),
+ new Performance(1, 19, "2017-11-02", 15000),
+ new Performance(2, 42, "2017-12-23", 7000));
+ // [END spanner_insert_data_with_timestamp_column]
+
+ // [START spanner_insert_datatypes_data]
+ static Value availableDates1 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-12-01"),
+ Date.parseDate("2020-12-02"),
+ Date.parseDate("2020-12-03")));
+ static Value availableDates2 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-11-01"),
+ Date.parseDate("2020-11-05"),
+ Date.parseDate("2020-11-15")));
+ static Value availableDates3 =
+ Value.dateArray(Arrays.asList(Date.parseDate("2020-10-01"), Date.parseDate("2020-10-07")));
+ static String exampleBytes1 = BaseEncoding.base64().encode("Hello World 1".getBytes());
+ static String exampleBytes2 = BaseEncoding.base64().encode("Hello World 2".getBytes());
+ static String exampleBytes3 = BaseEncoding.base64().encode("Hello World 3".getBytes());
+ static final List VENUES =
+ Arrays.asList(
+ new Venue(
+ 4,
+ "Venue 4",
+ exampleBytes1,
+ 1800,
+ availableDates1,
+ "2018-09-02",
+ false,
+ 0.85543f,
+ new BigDecimal("215100.10"),
+ Value.json(
+ "[{\"name\":\"room 1\",\"open\":true},{\"name\":\"room 2\",\"open\":false}]")),
+ new Venue(
+ 19,
+ "Venue 19",
+ exampleBytes2,
+ 6300,
+ availableDates2,
+ "2019-01-15",
+ true,
+ 0.98716f,
+ new BigDecimal("1200100.00"),
+ Value.json("{\"rating\":9,\"open\":true}")),
+ new Venue(
+ 42,
+ "Venue 42",
+ exampleBytes3,
+ 3000,
+ availableDates3,
+ "2018-10-01",
+ false,
+ 0.72598f,
+ new BigDecimal("390650.99"),
+ Value.json(
+ "{\"name\":null,"
+ + "\"open\":{\"Monday\":true,\"Tuesday\":false},"
+ + "\"tags\":[\"large\",\"airy\"]}")));
+ // [END spanner_insert_datatypes_data]
+
+ // [START spanner_create_database]
+ static void createDatabase(DatabaseAdminClient dbAdminClient,
+ InstanceName instanceName, String databaseId) {
+ CreateDatabaseRequest createDatabaseRequest =
+ CreateDatabaseRequest.newBuilder()
+ .setCreateStatement("CREATE DATABASE `" + databaseId + "`")
+ .setParent(instanceName.toString())
+ .addAllExtraStatements(Arrays.asList(
+ "CREATE TABLE Singers ("
+ + " SingerId INT64 NOT NULL,"
+ + " FirstName STRING(1024),"
+ + " LastName STRING(1024),"
+ + " SingerInfo BYTES(MAX),"
+ + " FullName STRING(2048) AS "
+ + " (ARRAY_TO_STRING([FirstName, LastName], \" \")) STORED"
+ + ") PRIMARY KEY (SingerId)",
+ "CREATE TABLE Albums ("
+ + " SingerId INT64 NOT NULL,"
+ + " AlbumId INT64 NOT NULL,"
+ + " AlbumTitle STRING(MAX)"
+ + ") PRIMARY KEY (SingerId, AlbumId),"
+ + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build();
+ try {
+ // Initiate the request which returns an OperationFuture.
+ com.google.spanner.admin.database.v1.Database db =
+ dbAdminClient.createDatabaseAsync(createDatabaseRequest).get();
+ System.out.println("Created database [" + db.getName() + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_create_database]
+
+ // [START spanner_create_table_with_timestamp_column]
+ static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient,
+ DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ dbAdminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "CREATE TABLE Performances ("
+ + " SingerId INT64 NOT NULL,"
+ + " VenueId INT64 NOT NULL,"
+ + " EventDate Date,"
+ + " Revenue INT64, "
+ + " LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)"
+ + ") PRIMARY KEY (SingerId, VenueId, EventDate),"
+ + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get();
+ System.out.println(
+ "Created Performances table in database: [" + databaseName.toString() + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_create_table_with_timestamp_column]
+
+ // [START spanner_insert_data_with_timestamp_column]
+ static void writeExampleDataWithTimestamp(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Performance performance : PERFORMANCES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Performances")
+ .set("SingerId")
+ .to(performance.singerId)
+ .set("VenueId")
+ .to(performance.venueId)
+ .set("EventDate")
+ .to(performance.eventDate)
+ .set("Revenue")
+ .to(performance.revenue)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_insert_data_with_timestamp_column]
+
+ // [START spanner_insert_data]
+ static void writeExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Singer singer : SINGERS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Singers")
+ .set("SingerId")
+ .to(singer.singerId)
+ .set("FirstName")
+ .to(singer.firstName)
+ .set("LastName")
+ .to(singer.lastName)
+ .build());
+ }
+ for (Album album : ALBUMS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Albums")
+ .set("SingerId")
+ .to(album.singerId)
+ .set("AlbumId")
+ .to(album.albumId)
+ .set("AlbumTitle")
+ .to(album.albumTitle)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_insert_data]
+
+ // [START spanner_delete_data]
+ static void deleteExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+
+ // KeySet.Builder can be used to delete a specific set of rows.
+ // Delete the Albums with the key values (2,1) and (2,3).
+ mutations.add(
+ Mutation.delete(
+ "Albums", KeySet.newBuilder().addKey(Key.of(2, 1)).addKey(Key.of(2, 3)).build()));
+
+ // KeyRange can be used to delete rows with a key in a specific range.
+ // Delete a range of rows where the column key is >=3 and <5
+ mutations.add(
+ Mutation.delete("Singers", KeySet.range(KeyRange.closedOpen(Key.of(3), Key.of(5)))));
+
+ // KeySet.all() can be used to delete all the rows in a table.
+ // Delete remaining Singers rows, which will also delete the remaining Albums rows since it was
+ // defined with ON DELETE CASCADE.
+ mutations.add(Mutation.delete("Singers", KeySet.all()));
+
+ dbClient.write(mutations);
+ System.out.printf("Records deleted.\n");
+ }
+ // [END spanner_delete_data]
+
+ // [START spanner_query_data]
+ static void query(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse() // Execute a single read or query against Cloud Spanner.
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_query_data]
+
+ // [START spanner_read_data]
+ static void read(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .read(
+ "Albums",
+ KeySet.all(), // Read all rows in a table.
+ Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_read_data]
+
+ // [START spanner_add_column]
+ static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64")).get();
+ System.out.println("Added MarketingBudget column");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_add_column]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64".
+ // [START spanner_update_data]
+ static void update(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(100000)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(500000)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_update_data]
+
+ // [START spanner_read_write_transaction]
+ static void writeWithTransaction(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ Struct row =
+ transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
+ long album2Budget = row.getLong(0);
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ long album1Budget =
+ transaction
+ .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
+ .getLong(0);
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(album1Budget)
+ .build());
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(album2Budget)
+ .build());
+ }
+ return null;
+ });
+ }
+ // [END spanner_read_write_transaction]
+
+ // [START spanner_query_data_with_new_column]
+ static void queryMarketingBudget(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_query_data_with_new_column]
+
+ // [START spanner_create_index]
+ static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)")).get();
+ System.out.println("Added AlbumsByAlbumTitle index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_create_index]
+
+ // Before running this example, add the index AlbumsByAlbumTitle by applying the DDL statement
+ // "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)".
+ // [START spanner_query_data_with_index]
+ static void queryUsingIndex(DatabaseClient dbClient) {
+ Statement statement =
+ Statement
+ // We use FORCE_INDEX hint to specify which index to use. For more details see
+ // https://cloud.google.com/spanner/docs/query-syntax#from-clause
+ .newBuilder(
+ "SELECT AlbumId, AlbumTitle, MarketingBudget "
+ + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} "
+ + "WHERE AlbumTitle >= @StartTitle AND AlbumTitle < @EndTitle")
+ // We use @BoundParameters to help speed up frequently executed queries.
+ // For more details see https://cloud.google.com/spanner/docs/sql-best-practices
+ .bind("StartTitle")
+ .to("Aardvark")
+ .bind("EndTitle")
+ .to("Goo")
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("AlbumId"),
+ resultSet.getString("AlbumTitle"),
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_query_data_with_index]
+
+ // [START spanner_read_data_with_index]
+ static void readUsingIndex(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
+ }
+ }
+ }
+ // [END spanner_read_data_with_index]
+
+ // [START spanner_create_storing_index]
+ static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
+ + "STORING (MarketingBudget)")).get();
+ System.out.println("Added AlbumsByAlbumTitle2 index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_create_storing_index]
+
+ // Before running this example, create a storing index AlbumsByAlbumTitle2 by applying the DDL
+ // statement "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)".
+ // [START spanner_read_data_with_storing_index]
+ static void readStoringIndex(DatabaseClient dbClient) {
+ // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle2",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong(0),
+ resultSet.getString(1),
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_read_data_with_storing_index]
+
+ // [START spanner_read_only_transaction]
+ static void readOnlyTransaction(DatabaseClient dbClient) {
+ // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
+ // We use a try-with-resource block to automatically do so.
+ try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
+ ResultSet queryResultSet =
+ transaction.executeQuery(
+ Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
+ while (queryResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2));
+ }
+ try (ResultSet readResultSet =
+ transaction.read(
+ "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (readResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2));
+ }
+ }
+ }
+ }
+ // [END spanner_read_only_transaction]
+
+ // [START spanner_read_stale_data]
+ static void readStaleData(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse(TimestampBound.ofExactStaleness(15, TimeUnit.SECONDS))
+ .read(
+ "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong(0),
+ resultSet.getLong(1),
+ resultSet.isNull(2) ? "NULL" : resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_read_stale_data]
+
+ // [START spanner_add_timestamp_column]
+ static void addCommitTimestamp(DatabaseAdminClient adminClient, DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ adminClient.updateDatabaseDdlAsync(
+ databaseName,
+ Arrays.asList(
+ "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP "
+ + "OPTIONS (allow_commit_timestamp=true)")).get();
+ System.out.println("Added LastUpdateTime as a commit timestamp column in Albums table.");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_add_timestamp_column]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64".
+ // In addition this update expects the LastUpdateTime column added by applying the DDL statement
+ // "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true)"
+ // [START spanner_update_data_with_timestamp_column]
+ static void updateWithTimestamp(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(1000000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(750000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_update_data_with_timestamp_column]
+
+ // [START spanner_query_data_with_timestamp_column]
+ static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime FROM Albums"
+ + " ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"),
+ resultSet.isNull("LastUpdateTime") ? "NULL" : resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_query_data_with_timestamp_column]
+
+ static void querySingersTable(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT SingerId, FirstName, LastName FROM Singers"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+
+ static void queryPerformancesTable(DatabaseClient dbClient) {
+ // Rows without an explicit value for Revenue will have a Revenue equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT SingerId, VenueId, EventDate, Revenue, LastUpdateTime "
+ + "FROM Performances ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("VenueId"),
+ resultSet.getDate("EventDate"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("Revenue") ? "NULL" : resultSet.getLong("Revenue"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+
+ // [START spanner_write_data_for_struct_queries]
+ static void writeStructExampleData(DatabaseClient dbClient) {
+ final List singers =
+ Arrays.asList(
+ new Singer(6, "Elena", "Campbell"),
+ new Singer(7, "Gabriel", "Wright"),
+ new Singer(8, "Benjamin", "Martinez"),
+ new Singer(9, "Hannah", "Harris"));
+
+ List mutations = new ArrayList<>();
+ for (Singer singer : singers) {
+ mutations.add(
+ Mutation.newInsertBuilder("Singers")
+ .set("SingerId")
+ .to(singer.singerId)
+ .set("FirstName")
+ .to(singer.firstName)
+ .set("LastName")
+ .to(singer.lastName)
+ .build());
+ }
+ dbClient.write(mutations);
+ System.out.println("Inserted example data for struct parameter queries.");
+ }
+ // [END spanner_write_data_for_struct_queries]
+
+ static void queryWithStruct(DatabaseClient dbClient) {
+ // [START spanner_create_struct_with_data]
+ Struct name =
+ Struct.newBuilder().set("FirstName").to("Elena").set("LastName").to("Campbell").build();
+ // [END spanner_create_struct_with_data]
+
+ // [START spanner_query_data_with_struct]
+ Statement s =
+ Statement.newBuilder(
+ "SELECT SingerId FROM Singers "
+ + "WHERE STRUCT(FirstName, LastName) "
+ + "= @name")
+ .bind("name")
+ .to(name)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) {
+ while (resultSet.next()) {
+ System.out.printf("%d\n", resultSet.getLong("SingerId"));
+ }
+ }
+ // [END spanner_query_data_with_struct]
+ }
+
+ static void queryWithArrayOfStruct(DatabaseClient dbClient) {
+ // [START spanner_create_user_defined_struct]
+ Type nameType =
+ Type.struct(
+ Arrays.asList(
+ StructField.of("FirstName", Type.string()),
+ StructField.of("LastName", Type.string())));
+ // [END spanner_create_user_defined_struct]
+
+ // [START spanner_create_array_of_struct_with_data]
+ List bandMembers = new ArrayList<>();
+ bandMembers.add(
+ Struct.newBuilder().set("FirstName").to("Elena").set("LastName").to("Campbell").build());
+ bandMembers.add(
+ Struct.newBuilder().set("FirstName").to("Gabriel").set("LastName").to("Wright").build());
+ bandMembers.add(
+ Struct.newBuilder().set("FirstName").to("Benjamin").set("LastName").to("Martinez").build());
+ // [END spanner_create_array_of_struct_with_data]
+
+ // [START spanner_query_data_with_array_of_struct]
+ Statement s =
+ Statement.newBuilder(
+ "SELECT SingerId FROM Singers WHERE "
+ + "STRUCT(FirstName, LastName) "
+ + "IN UNNEST(@names) "
+ + "ORDER BY SingerId DESC")
+ .bind("names")
+ .toStructArray(nameType, bandMembers)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) {
+ while (resultSet.next()) {
+ System.out.printf("%d\n", resultSet.getLong("SingerId"));
+ }
+ }
+ // [END spanner_query_data_with_array_of_struct]
+ }
+
+ // [START spanner_field_access_on_struct_parameters]
+ static void queryStructField(DatabaseClient dbClient) {
+ Statement s =
+ Statement.newBuilder("SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName")
+ .bind("name")
+ .to(
+ Struct.newBuilder()
+ .set("FirstName")
+ .to("Elena")
+ .set("LastName")
+ .to("Campbell")
+ .build())
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) {
+ while (resultSet.next()) {
+ System.out.printf("%d\n", resultSet.getLong("SingerId"));
+ }
+ }
+ }
+ // [END spanner_field_access_on_struct_parameters]
+
+ // [START spanner_field_access_on_nested_struct_parameters]
+ static void queryNestedStructField(DatabaseClient dbClient) {
+ Type nameType =
+ Type.struct(
+ Arrays.asList(
+ StructField.of("FirstName", Type.string()),
+ StructField.of("LastName", Type.string())));
+
+ Struct songInfo =
+ Struct.newBuilder()
+ .set("song_name")
+ .to("Imagination")
+ .set("artistNames")
+ .toStructArray(
+ nameType,
+ Arrays.asList(
+ Struct.newBuilder()
+ .set("FirstName")
+ .to("Elena")
+ .set("LastName")
+ .to("Campbell")
+ .build(),
+ Struct.newBuilder()
+ .set("FirstName")
+ .to("Hannah")
+ .set("LastName")
+ .to("Harris")
+ .build()))
+ .build();
+ Statement s =
+ Statement.newBuilder(
+ "SELECT SingerId, @song_info.song_name "
+ + "FROM Singers WHERE "
+ + "STRUCT(FirstName, LastName) "
+ + "IN UNNEST(@song_info.artistNames)")
+ .bind("song_info")
+ .to(songInfo)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) {
+ while (resultSet.next()) {
+ System.out.printf("%d %s\n", resultSet.getLong("SingerId"), resultSet.getString(1));
+ }
+ }
+ }
+ // [END spanner_field_access_on_nested_struct_parameters]
+
+ // [START spanner_dml_standard_insert]
+ static void insertUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (10, 'Virginia', 'Watson')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_standard_insert]
+
+ // [START spanner_dml_standard_update]
+ static void updateUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 1";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record updated.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_standard_update]
+
+ // [START spanner_dml_standard_delete]
+ static void deleteUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql = "DELETE FROM Singers WHERE FirstName = 'Alice'";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record deleted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_standard_delete]
+
+ // [START spanner_dml_standard_update_with_timestamp]
+ static void updateUsingDmlWithTimestamp(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "UPDATE Albums "
+ + "SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() WHERE SingerId = 1";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d records updated.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_standard_update_with_timestamp]
+
+ // [START spanner_dml_write_then_read]
+ static void writeAndReadUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Insert record.
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (11, 'Timothy', 'Campbell')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ // Read newly inserted record.
+ sql = "SELECT FirstName, LastName FROM Singers WHERE SingerId = 11";
+ // We use a try-with-resource block to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet = transaction.executeQuery(Statement.of(sql))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s\n",
+ resultSet.getString("FirstName"), resultSet.getString("LastName"));
+ }
+ }
+ return null;
+ });
+ }
+ // [END spanner_dml_write_then_read]
+
+ // [START spanner_dml_structs]
+ static void updateUsingDmlWithStruct(DatabaseClient dbClient) {
+ Struct name =
+ Struct.newBuilder().set("FirstName").to("Timothy").set("LastName").to("Campbell").build();
+ Statement s =
+ Statement.newBuilder(
+ "UPDATE Singers SET LastName = 'Grant' "
+ + "WHERE STRUCT(FirstName, LastName) "
+ + "= @name")
+ .bind("name")
+ .to(name)
+ .build();
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ long rowCount = transaction.executeUpdate(s);
+ System.out.printf("%d record updated.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_structs]
+
+ // [START spanner_dml_getting_started_insert]
+ static void writeUsingDml(DatabaseClient dbClient) {
+ // Insert 4 singer records
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
+ + "(12, 'Melissa', 'Garcia'), "
+ + "(13, 'Russell', 'Morales'), "
+ + "(14, 'Jacqueline', 'Long'), "
+ + "(15, 'Dylan', 'Shaw')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d records inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_dml_getting_started_insert]
+
+ // [START spanner_query_with_parameter]
+ static void queryWithParameter(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT SingerId, FirstName, LastName "
+ + "FROM Singers "
+ + "WHERE LastName = @lastName")
+ .bind("lastName")
+ .to("Garcia")
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+ // [END spanner_query_with_parameter]
+
+ // [START spanner_dml_getting_started_update]
+ static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ String sql1 =
+ "SELECT MarketingBudget from Albums WHERE SingerId = 2 and AlbumId = 2";
+ ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
+ long album2Budget = 0;
+ while (resultSet.next()) {
+ album2Budget = resultSet.getLong("MarketingBudget");
+ }
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ String sql2 =
+ "SELECT MarketingBudget from Albums WHERE SingerId = 1 and AlbumId = 1";
+ ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
+ long album1Budget = 0;
+ while (resultSet2.next()) {
+ album1Budget = resultSet2.getLong("MarketingBudget");
+ }
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ Statement updateStatement =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = @AlbumBudget "
+ + "WHERE SingerId = 1 and AlbumId = 1")
+ .bind("AlbumBudget")
+ .to(album1Budget)
+ .build();
+ transaction.executeUpdate(updateStatement);
+ Statement updateStatement2 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = @AlbumBudget "
+ + "WHERE SingerId = 2 and AlbumId = 2")
+ .bind("AlbumBudget")
+ .to(album2Budget)
+ .build();
+ transaction.executeUpdate(updateStatement2);
+ }
+ return null;
+ });
+ }
+ // [END spanner_dml_getting_started_update]
+
+ // [START spanner_dml_partitioned_update]
+ static void updateUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records updated.\n", rowCount);
+ }
+ // [END spanner_dml_partitioned_update]
+
+ // [START spanner_dml_partitioned_delete]
+ static void deleteUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "DELETE FROM Singers WHERE SingerId > 10";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records deleted.\n", rowCount);
+ }
+ // [END spanner_dml_partitioned_delete]
+
+ // [START spanner_dml_batch_update]
+ static void updateUsingBatchDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ List stmts = new ArrayList();
+ String sql =
+ "INSERT INTO Albums "
+ + "(SingerId, AlbumId, AlbumTitle, MarketingBudget) "
+ + "VALUES (1, 3, 'Test Album Title', 10000) ";
+ stmts.add(Statement.of(sql));
+ sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 3";
+ stmts.add(Statement.of(sql));
+ long[] rowCounts;
+ try {
+ rowCounts = transaction.batchUpdate(stmts);
+ } catch (SpannerBatchUpdateException e) {
+ rowCounts = e.getUpdateCounts();
+ }
+ for (int i = 0; i < rowCounts.length; i++) {
+ System.out.printf("%d record updated by stmt %d.\n", rowCounts[i], i);
+ }
+ return null;
+ });
+ }
+ // [END spanner_dml_batch_update]
+
+ // [START spanner_create_table_with_datatypes]
+ static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient,
+ DatabaseName databaseName) {
+ try {
+ // Initiate the request which returns an OperationFuture.
+ dbAdminClient.updateDatabaseDdlAsync(databaseName,
+ Arrays.asList(
+ "CREATE TABLE Venues ("
+ + " VenueId INT64 NOT NULL,"
+ + " VenueName STRING(100),"
+ + " VenueInfo BYTES(MAX),"
+ + " Capacity INT64,"
+ + " AvailableDates ARRAY,"
+ + " LastContactDate DATE,"
+ + " OutdoorVenue BOOL, "
+ + " PopularityScore FLOAT64, "
+ + " Revenue NUMERIC, "
+ + " VenueDetails JSON, "
+ + " LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)"
+ + ") PRIMARY KEY (VenueId)")).get();
+ System.out.println("Created Venues table in database: [" + databaseName.toString() + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_create_table_with_datatypes]
+
+ // [START spanner_insert_datatypes_data]
+ static void writeDatatypesData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Venue venue : VENUES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Venues")
+ .set("VenueId")
+ .to(venue.venueId)
+ .set("VenueName")
+ .to(venue.venueName)
+ .set("VenueInfo")
+ .to(venue.venueInfo)
+ .set("Capacity")
+ .to(venue.capacity)
+ .set("AvailableDates")
+ .to(venue.availableDates)
+ .set("LastContactDate")
+ .to(venue.lastContactDate)
+ .set("OutdoorVenue")
+ .to(venue.outdoorVenue)
+ .set("PopularityScore")
+ .to(venue.popularityScore)
+ .set("Revenue")
+ .to(venue.revenue)
+ .set("VenueDetails")
+ .to(venue.venueDetails)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_insert_datatypes_data]
+
+ // [START spanner_query_with_array_parameter]
+ static void queryWithArray(DatabaseClient dbClient) {
+ Value exampleArray =
+ Value.dateArray(Arrays.asList(Date.parseDate("2020-10-01"), Date.parseDate("2020-11-01")));
+
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, AvailableDate FROM Venues v, "
+ + "UNNEST(v.AvailableDates) as AvailableDate "
+ + "WHERE AvailableDate in UNNEST(@availableDates)")
+ .bind("availableDates")
+ .to(exampleArray)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getDate("AvailableDate"));
+ }
+ }
+ }
+ // [END spanner_query_with_array_parameter]
+
+ // [START spanner_query_with_bool_parameter]
+ static void queryWithBool(DatabaseClient dbClient) {
+ boolean exampleBool = true;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, OutdoorVenue FROM Venues "
+ + "WHERE OutdoorVenue = @outdoorVenue")
+ .bind("outdoorVenue")
+ .to(exampleBool)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %b\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getBoolean("OutdoorVenue"));
+ }
+ }
+ }
+ // [END spanner_query_with_bool_parameter]
+
+ // [START spanner_query_with_bytes_parameter]
+ static void queryWithBytes(DatabaseClient dbClient) {
+ ByteArray exampleBytes =
+ ByteArray.fromBase64(BaseEncoding.base64().encode("Hello World 1".getBytes()));
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName FROM Venues " + "WHERE VenueInfo = @venueInfo")
+ .bind("venueInfo")
+ .to(exampleBytes)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_query_with_bytes_parameter]
+
+ // [START spanner_query_with_date_parameter]
+ static void queryWithDate(DatabaseClient dbClient) {
+ String exampleDate = "2019-01-01";
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, LastContactDate FROM Venues "
+ + "WHERE LastContactDate < @lastContactDate")
+ .bind("lastContactDate")
+ .to(exampleDate)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getDate("LastContactDate"));
+ }
+ }
+ }
+ // [END spanner_query_with_date_parameter]
+
+ // [START spanner_query_with_float_parameter]
+ static void queryWithFloat(DatabaseClient dbClient) {
+ float exampleFloat = 0.8f;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, PopularityScore FROM Venues "
+ + "WHERE PopularityScore > @popularityScore")
+ .bind("popularityScore")
+ .to(exampleFloat)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %f\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getDouble("PopularityScore"));
+ }
+ }
+ }
+ // [END spanner_query_with_float_parameter]
+
+ // [START spanner_query_with_int_parameter]
+ static void queryWithInt(DatabaseClient dbClient) {
+ long exampleInt = 3000;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, Capacity FROM Venues " + "WHERE Capacity >= @capacity")
+ .bind("capacity")
+ .to(exampleInt)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %d\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getLong("Capacity"));
+ }
+ }
+ }
+ // [END spanner_query_with_int_parameter]
+
+ // [START spanner_query_with_string_parameter]
+ static void queryWithString(DatabaseClient dbClient) {
+ String exampleString = "Venue 42";
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName FROM Venues " + "WHERE VenueName = @venueName")
+ .bind("venueName")
+ .to(exampleString)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_query_with_string_parameter]
+
+ // [START spanner_query_with_timestamp_parameter]
+ static void queryWithTimestampParameter(DatabaseClient dbClient) {
+ Instant exampleTimestamp = Instant.now();
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, LastUpdateTime FROM Venues "
+ + "WHERE LastUpdateTime < @lastUpdateTime")
+ .bind("lastUpdateTime")
+ .to(exampleTimestamp.toString())
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_query_with_timestamp_parameter]
+
+ // [START spanner_query_with_numeric_parameter]
+ static void queryWithNumeric(DatabaseClient dbClient) {
+ BigDecimal exampleNumeric = new BigDecimal("300000");
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT VenueId, VenueName, Revenue\n"
+ + "FROM Venues\n"
+ + "WHERE Revenue >= @revenue")
+ .bind("revenue")
+ .to(exampleNumeric)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s%n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getBigDecimal("Revenue"));
+ }
+ }
+ }
+ // [END spanner_query_with_numeric_parameter]
+
+ // [START spanner_create_client_with_query_options]
+ static void clientWithQueryOptions(DatabaseId db) {
+ SpannerOptions options =
+ SpannerOptions.newBuilder()
+ .setDefaultQueryOptions(
+ db, QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying the
+ // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build();
+ Spanner spanner = options.getService();
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_create_client_with_query_options]
+
+ // [START spanner_query_with_query_options]
+ static void queryWithQueryOptions(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement
+ .newBuilder("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")
+ .withQueryOptions(QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying the
+ // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build())) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_query_with_query_options]
+
+ // [START spanner_create_backup]
+ static void createBackup(DatabaseAdminClient dbAdminClient, String projectId, String instanceId,
+ String databaseId, String backupId, Timestamp versionTime) {
+ // Set expire time to 14 days from now.
+ Timestamp expireTime =
+ Timestamp.newBuilder().setSeconds(TimeUnit.MILLISECONDS.toSeconds((
+ System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14)))).build();
+ BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+ Backup backup = Backup.newBuilder()
+ .setName(backupName.toString())
+ .setDatabase(DatabaseName.of(projectId, instanceId, databaseId).toString())
+ .setExpireTime(expireTime).setVersionTime(versionTime).build();
+
+ // Initiate the request which returns an OperationFuture.
+ System.out.println("Creating backup [" + backupId + "]...");
+ try {
+ // Wait for the backup operation to complete.
+ backup = dbAdminClient.createBackupAsync(
+ InstanceName.of(projectId, instanceId), backup, backupId).get();
+ System.out.println("Created backup [" + backup.getName() + "]");
+ } catch (ExecutionException e) {
+ throw SpannerExceptionFactory.asSpannerException(e);
+ } catch (InterruptedException e) {
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+
+ // Reload the metadata of the backup from the server.
+ backup = dbAdminClient.getBackup(backup.getName());
+ System.out.println(
+ String.format(
+ "Backup %s of size %d bytes was created at %s for version of database at %s",
+ backup.getName(),
+ backup.getSizeBytes(),
+ java.time.OffsetDateTime.ofInstant(
+ Instant.ofEpochSecond(backup.getCreateTime().getSeconds(),
+ backup.getCreateTime().getNanos()), ZoneId.systemDefault()),
+ java.time.OffsetDateTime.ofInstant(
+ Instant.ofEpochSecond(backup.getVersionTime().getSeconds(),
+ backup.getVersionTime().getNanos()), ZoneId.systemDefault()))
+ );
+ }
+ // [END spanner_create_backup]
+
+ // [START spanner_cancel_backup_create]
+ static void cancelCreateBackup(
+ DatabaseAdminClient dbAdminClient, String projectId, String instanceId,
+ String databaseId, String backupId) {
+ // Set expire time to 14 days from now.
+ Timestamp expireTime =
+ Timestamp.newBuilder().setSeconds(TimeUnit.MILLISECONDS.toSeconds((
+ System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14)))).build();
+ BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+ Backup backup = Backup.newBuilder()
+ .setName(backupName.toString())
+ .setDatabase(DatabaseName.of(projectId, instanceId, databaseId).toString())
+ .setExpireTime(expireTime).build();
+
+ try {
+ // Start the creation of a backup.
+ System.out.println("Creating backup [" + backupId + "]...");
+ OperationFuture op = dbAdminClient.createBackupAsync(
+ InstanceName.of(projectId, instanceId), backup, backupId);
+
+ // Try to cancel the backup operation.
+ System.out.println("Cancelling create backup operation for [" + backupId + "]...");
+ dbAdminClient.getOperationsClient().cancelOperation(op.getName());
+
+ // Get a polling future for the running operation. This future will regularly poll the server
+ // for the current status of the backup operation.
+ RetryingFuture pollingFuture = op.getPollingFuture();
+
+ // Wait for the operation to finish.
+ // isDone will return true when the operation is complete, regardless of whether it was
+ // successful or not.
+ while (!pollingFuture.get().isDone()) {
+ System.out.println("Waiting for the cancelled backup operation to finish...");
+ Thread.sleep(TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS));
+ }
+ if (pollingFuture.get().getErrorCode() == null) {
+ // Backup was created before it could be cancelled. Delete the backup.
+ dbAdminClient.deleteBackup(backupName);
+ System.out.println("Backup operation for [" + backupId
+ + "] successfully finished before it could be cancelled");
+ } else if (pollingFuture.get().getErrorCode().getCode() == StatusCode.Code.CANCELLED) {
+ System.out.println("Backup operation for [" + backupId + "] successfully cancelled");
+ }
+ } catch (ExecutionException e) {
+ throw SpannerExceptionFactory.newSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_cancel_backup_create]
+
+ // [START spanner_list_backup_operations]
+ static void listBackupOperations(
+ DatabaseAdminClient databaseAdminClient,
+ String projectId, String instanceId,
+ String databaseId, String backupId) {
+ InstanceName instanceName = InstanceName.of(projectId, instanceId);
+ // Get 'CreateBackup' operations for the sample database.
+ String filter =
+ String.format(
+ "(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.CreateBackupMetadata) "
+ + "AND (metadata.database:%s)",
+ DatabaseName.of(projectId, instanceId, databaseId).toString());
+ ListBackupOperationsRequest listBackupOperationsRequest =
+ ListBackupOperationsRequest.newBuilder()
+ .setParent(instanceName.toString()).setFilter(filter).build();
+ ListBackupOperationsPagedResponse createBackupOperations
+ = databaseAdminClient.listBackupOperations(listBackupOperationsRequest);
+ System.out.println("Create Backup Operations:");
+ for (Operation op : createBackupOperations.iterateAll()) {
+ try {
+ CreateBackupMetadata metadata = op.getMetadata().unpack(CreateBackupMetadata.class);
+ System.out.println(
+ String.format(
+ "Backup %s on database %s pending: %d%% complete",
+ metadata.getName(),
+ metadata.getDatabase(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain CreateBackupMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ // Get copy backup operations for the sample database.
+ filter = String.format(
+ "(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.CopyBackupMetadata) "
+ + "AND (metadata.source_backup:%s)",
+ BackupName.of(projectId, instanceId, backupId).toString());
+ listBackupOperationsRequest =
+ ListBackupOperationsRequest.newBuilder()
+ .setParent(instanceName.toString()).setFilter(filter).build();
+ ListBackupOperationsPagedResponse copyBackupOperations =
+ databaseAdminClient.listBackupOperations(listBackupOperationsRequest);
+ System.out.println("Copy Backup Operations:");
+ for (Operation op : copyBackupOperations.iterateAll()) {
+ try {
+ CopyBackupMetadata copyBackupMetadata =
+ op.getMetadata().unpack(CopyBackupMetadata.class);
+ System.out.println(
+ String.format(
+ "Copy Backup %s on backup %s pending: %d%% complete",
+ copyBackupMetadata.getName(),
+ copyBackupMetadata.getSourceBackup(),
+ copyBackupMetadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain CopyBackupMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_list_backup_operations]
+
+ // [START spanner_list_database_operations]
+ static void listDatabaseOperations(
+ DatabaseAdminClient dbAdminClient, String projectId, String instanceId) {
+ // Get optimize restored database operations.
+ com.google.cloud.Timestamp last24Hours = com.google.cloud.Timestamp.ofTimeSecondsAndNanos(
+ TimeUnit.SECONDS.convert(
+ TimeUnit.HOURS.convert(com.google.cloud.Timestamp.now().getSeconds(), TimeUnit.SECONDS)
+ - 24,
+ TimeUnit.HOURS), 0);
+ String filter = String.format("(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata) AND "
+ + "(metadata.progress.start_time > \"%s\")", last24Hours);
+ ListDatabaseOperationsRequest listDatabaseOperationsRequest =
+ ListDatabaseOperationsRequest.newBuilder()
+ .setParent(com.google.spanner.admin.instance.v1.InstanceName.of(
+ projectId, instanceId).toString()).setFilter(filter).build();
+ ListDatabaseOperationsPagedResponse pagedResponse
+ = dbAdminClient.listDatabaseOperations(listDatabaseOperationsRequest);
+ for (Operation op : pagedResponse.iterateAll()) {
+ try {
+ OptimizeRestoredDatabaseMetadata metadata =
+ op.getMetadata().unpack(OptimizeRestoredDatabaseMetadata.class);
+ System.out.println(String.format(
+ "Database %s restored from backup is %d%% optimized",
+ metadata.getName(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain OptimizeRestoredDatabaseMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_list_database_operations]
+
+ // [START spanner_list_backups]
+ static void listBackups(
+ DatabaseAdminClient dbAdminClient, String projectId,
+ String instanceId, String databaseId, String backupId) {
+ InstanceName instanceName = InstanceName.of(projectId, instanceId);
+ // List all backups.
+ System.out.println("All backups:");
+ for (Backup backup : dbAdminClient.listBackups(
+ instanceName.toString()).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List all backups with a specific name.
+ System.out.println(
+ String.format("All backups with backup name containing \"%s\":", backupId));
+ ListBackupsRequest listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString())
+ .setFilter(String.format("name:%s", backupId)).build();
+ for (Backup backup : dbAdminClient.listBackups(listBackupsRequest).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List all backups for databases whose name contains a certain text.
+ System.out.println(
+ String.format(
+ "All backups for databases with a name containing \"%s\":", databaseId));
+ listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString())
+ .setFilter(String.format("database:%s", databaseId)).build();
+ for (Backup backup : dbAdminClient.listBackups(listBackupsRequest).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List all backups that expire before a certain time.
+ com.google.cloud.Timestamp expireTime = com.google.cloud.Timestamp.ofTimeMicroseconds(
+ TimeUnit.MICROSECONDS.convert(
+ System.currentTimeMillis() + TimeUnit.DAYS.toMillis(30), TimeUnit.MILLISECONDS));
+
+ System.out.println(String.format("All backups that expire before %s:", expireTime));
+ listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString())
+ .setFilter(String.format("expire_time < \"%s\"", expireTime)).build();
+
+ for (Backup backup : dbAdminClient.listBackups(listBackupsRequest).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List all backups with size greater than a certain number of bytes.
+ listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString())
+ .setFilter("size_bytes > 100").build();
+
+ System.out.println("All backups with size greater than 100 bytes:");
+ for (Backup backup : dbAdminClient.listBackups(listBackupsRequest).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List all backups with a create time after a certain timestamp and that are also ready.
+ com.google.cloud.Timestamp createTime = com.google.cloud.Timestamp.ofTimeMicroseconds(
+ TimeUnit.MICROSECONDS.convert(
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS));
+
+ System.out.println(
+ String.format(
+ "All databases created after %s and that are ready:", createTime.toString()));
+ listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString())
+ .setFilter(String.format(
+ "create_time >= \"%s\" AND state:READY", createTime.toString())).build();
+ for (Backup backup : dbAdminClient.listBackups(listBackupsRequest).iterateAll()) {
+ System.out.println(backup);
+ }
+
+ // List backups using pagination.
+ System.out.println("All backups, listed using pagination:");
+ listBackupsRequest =
+ ListBackupsRequest.newBuilder().setParent(instanceName.toString()).setPageSize(10).build();
+ while (true) {
+ ListBackupsPagedResponse response = dbAdminClient.listBackups(listBackupsRequest);
+ for (Backup backup : response.getPage().iterateAll()) {
+ System.out.println(backup);
+ }
+ String nextPageToken = response.getNextPageToken();
+ if (!Strings.isNullOrEmpty(nextPageToken)) {
+ listBackupsRequest = listBackupsRequest.toBuilder().setPageToken(nextPageToken).build();
+ } else {
+ break;
+ }
+ }
+ }
+ // [END spanner_list_backups]
+
+ // [START spanner_restore_backup]
+ static void restoreBackup(
+ DatabaseAdminClient dbAdminClient,
+ String projectId,
+ String instanceId,
+ String backupId,
+ String restoreToDatabaseId) {
+ BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+ Backup backup = dbAdminClient.getBackup(backupName);
+ // Initiate the request which returns an OperationFuture.
+ System.out.println(String.format(
+ "Restoring backup [%s] to database [%s]...", backup.getName(), restoreToDatabaseId));
+ try {
+ RestoreDatabaseRequest request =
+ RestoreDatabaseRequest.newBuilder()
+ .setParent(InstanceName.of(projectId, instanceId).toString())
+ .setDatabaseId(restoreToDatabaseId)
+ .setBackup(backupName.toString()).build();
+ OperationFuture op =
+ dbAdminClient.restoreDatabaseAsync(request);
+ // Wait until the database has been restored.
+ com.google.spanner.admin.database.v1.Database db = op.get();
+ // Get the restore info.
+ RestoreInfo restoreInfo = db.getRestoreInfo();
+ BackupInfo backupInfo = restoreInfo.getBackupInfo();
+
+ System.out.println(
+ "Restored database ["
+ + db.getName()
+ + "] from ["
+ + restoreInfo.getBackupInfo().getBackup()
+ + "] with version time [" + backupInfo.getVersionTime() + "]");
+ } catch (ExecutionException e) {
+ throw SpannerExceptionFactory.newSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_restore_backup]
+
+ // [START spanner_update_backup]
+ static void updateBackup(DatabaseAdminClient dbAdminClient, String projectId,
+ String instanceId, String backupId) {
+ BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+
+ // Get current backup metadata.
+ Backup backup = dbAdminClient.getBackup(backupName);
+ // Add 30 days to the expire time.
+ // Expire time must be within 366 days of the create time of the backup.
+ Timestamp currentExpireTime = backup.getExpireTime();
+ com.google.cloud.Timestamp newExpireTime =
+ com.google.cloud.Timestamp.ofTimeMicroseconds(
+ TimeUnit.SECONDS.toMicros(currentExpireTime.getSeconds())
+ + TimeUnit.NANOSECONDS.toMicros(currentExpireTime.getNanos())
+ + TimeUnit.DAYS.toMicros(30L));
+
+ // New Expire Time must be less than Max Expire Time
+ newExpireTime =
+ newExpireTime.compareTo(com.google.cloud.Timestamp.fromProto(backup.getMaxExpireTime()))
+ < 0 ? newExpireTime : com.google.cloud.Timestamp.fromProto(backup.getMaxExpireTime());
+
+ System.out.println(String.format(
+ "Updating expire time of backup [%s] to %s...",
+ backupId.toString(),
+ java.time.OffsetDateTime.ofInstant(
+ Instant.ofEpochSecond(newExpireTime.getSeconds(),
+ newExpireTime.getNanos()), ZoneId.systemDefault())));
+
+ // Update expire time.
+ backup = backup.toBuilder().setExpireTime(newExpireTime.toProto()).build();
+ dbAdminClient.updateBackup(backup,
+ FieldMask.newBuilder().addAllPaths(Lists.newArrayList("expire_time")).build());
+ System.out.println("Updated backup [" + backupId + "]");
+ }
+ // [END spanner_update_backup]
+
+ // [START spanner_delete_backup]
+ static void deleteBackup(DatabaseAdminClient dbAdminClient,
+ String project, String instance, String backupId) {
+ BackupName backupName = BackupName.of(project, instance, backupId);
+
+ // Delete the backup.
+ System.out.println("Deleting backup [" + backupId + "]...");
+ dbAdminClient.deleteBackup(backupName);
+ // Verify that the backup is deleted.
+ try {
+ dbAdminClient.getBackup(backupName);
+ } catch (NotFoundException e) {
+ if (e.getStatusCode().getCode() == Code.NOT_FOUND) {
+ System.out.println("Deleted backup [" + backupId + "]");
+ } else {
+ System.out.println("Delete backup [" + backupId + "] failed");
+ throw new RuntimeException("Delete backup [" + backupId + "] failed", e);
+ }
+ }
+ }
+ // [END spanner_delete_backup]
+
+ static void run(
+ DatabaseClient dbClient,
+ DatabaseAdminClient dbAdminClient,
+ String command,
+ DatabaseId database,
+ String backupId) {
+ switch (command) {
+ case "createdatabase":
+ createDatabase(dbAdminClient, InstanceName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance()), database.getDatabase());
+ break;
+ case "write":
+ writeExampleData(dbClient);
+ break;
+ case "delete":
+ deleteExampleData(dbClient);
+ break;
+ case "query":
+ query(dbClient);
+ break;
+ case "read":
+ read(dbClient);
+ break;
+ case "addmarketingbudget":
+ addMarketingBudget(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "update":
+ update(dbClient);
+ break;
+ case "writetransaction":
+ writeWithTransaction(dbClient);
+ break;
+ case "querymarketingbudget":
+ queryMarketingBudget(dbClient);
+ break;
+ case "addindex":
+ addIndex(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "readindex":
+ readUsingIndex(dbClient);
+ break;
+ case "queryindex":
+ queryUsingIndex(dbClient);
+ break;
+ case "addstoringindex":
+ addStoringIndex(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "readstoringindex":
+ readStoringIndex(dbClient);
+ break;
+ case "readonlytransaction":
+ readOnlyTransaction(dbClient);
+ break;
+ case "readstaledata":
+ readStaleData(dbClient);
+ break;
+ case "addcommittimestamp":
+ addCommitTimestamp(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "updatewithtimestamp":
+ updateWithTimestamp(dbClient);
+ break;
+ case "querywithtimestamp":
+ queryMarketingBudgetWithTimestamp(dbClient);
+ break;
+ case "createtablewithtimestamp":
+ createTableWithTimestamp(dbAdminClient,
+ DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "writewithtimestamp":
+ writeExampleDataWithTimestamp(dbClient);
+ break;
+ case "querysingerstable":
+ querySingersTable(dbClient);
+ break;
+ case "queryperformancestable":
+ queryPerformancesTable(dbClient);
+ break;
+ case "writestructdata":
+ writeStructExampleData(dbClient);
+ break;
+ case "querywithstruct":
+ queryWithStruct(dbClient);
+ break;
+ case "querywitharrayofstruct":
+ queryWithArrayOfStruct(dbClient);
+ break;
+ case "querystructfield":
+ queryStructField(dbClient);
+ break;
+ case "querynestedstructfield":
+ queryNestedStructField(dbClient);
+ break;
+ case "insertusingdml":
+ insertUsingDml(dbClient);
+ break;
+ case "updateusingdml":
+ updateUsingDml(dbClient);
+ break;
+ case "deleteusingdml":
+ deleteUsingDml(dbClient);
+ break;
+ case "updateusingdmlwithtimestamp":
+ updateUsingDmlWithTimestamp(dbClient);
+ break;
+ case "writeandreadusingdml":
+ writeAndReadUsingDml(dbClient);
+ break;
+ case "updateusingdmlwithstruct":
+ updateUsingDmlWithStruct(dbClient);
+ break;
+ case "writeusingdml":
+ writeUsingDml(dbClient);
+ break;
+ case "querywithparameter":
+ queryWithParameter(dbClient);
+ break;
+ case "writewithtransactionusingdml":
+ writeWithTransactionUsingDml(dbClient);
+ break;
+ case "updateusingpartitioneddml":
+ updateUsingPartitionedDml(dbClient);
+ break;
+ case "deleteusingpartitioneddml":
+ deleteUsingPartitionedDml(dbClient);
+ break;
+ case "updateusingbatchdml":
+ updateUsingBatchDml(dbClient);
+ break;
+ case "createtablewithdatatypes":
+ createTableWithDatatypes(dbAdminClient,
+ DatabaseName.of(database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase()));
+ break;
+ case "writedatatypesdata":
+ writeDatatypesData(dbClient);
+ break;
+ case "querywitharray":
+ queryWithArray(dbClient);
+ break;
+ case "querywithbool":
+ queryWithBool(dbClient);
+ break;
+ case "querywithbytes":
+ queryWithBytes(dbClient);
+ break;
+ case "querywithdate":
+ queryWithDate(dbClient);
+ break;
+ case "querywithfloat":
+ queryWithFloat(dbClient);
+ break;
+ case "querywithint":
+ queryWithInt(dbClient);
+ break;
+ case "querywithstring":
+ queryWithString(dbClient);
+ break;
+ case "querywithtimestampparameter":
+ queryWithTimestampParameter(dbClient);
+ break;
+ case "querywithnumeric":
+ queryWithNumeric(dbClient);
+ break;
+ case "clientwithqueryoptions":
+ clientWithQueryOptions(database);
+ break;
+ case "querywithqueryoptions":
+ queryWithQueryOptions(dbClient);
+ break;
+ case "createbackup":
+ createBackup(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase(),
+ backupId, getVersionTime(dbClient));
+ break;
+ case "cancelcreatebackup":
+ cancelCreateBackup(
+ dbAdminClient,
+ database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase(),
+ backupId + "_cancel");
+ break;
+ case "listbackupoperations":
+ listBackupOperations(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase(), backupId);
+ break;
+ case "listdatabaseoperations":
+ listDatabaseOperations(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance());
+ break;
+ case "listbackups":
+ listBackups(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), database.getDatabase(), backupId);
+ break;
+ case "restorebackup":
+ restoreBackup(
+ dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), backupId, database.getDatabase());
+ break;
+ case "updatebackup":
+ updateBackup(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), backupId);
+ break;
+ case "deletebackup":
+ deleteBackup(dbAdminClient, database.getInstanceId().getProject(),
+ database.getInstanceId().getInstance(), backupId);
+ break;
+ default:
+ printUsageAndExit();
+ }
+ }
+
+ static Timestamp getVersionTime(DatabaseClient dbClient) {
+ // Generates a version time for the backup
+ com.google.cloud.Timestamp versionTime;
+ try (ResultSet resultSet = dbClient.singleUse()
+ .executeQuery(Statement.of("SELECT CURRENT_TIMESTAMP()"))) {
+ resultSet.next();
+ versionTime = resultSet.getTimestamp(0);
+ }
+ return versionTime.toProto();
+ }
+
+ static void printUsageAndExit() {
+ System.err.println("Usage:");
+ System.err.println(" SpannerExample ");
+ System.err.println("");
+ System.err.println("Examples:");
+ System.err.println(" SpannerExample createdatabase my-instance example-db");
+ System.err.println(" SpannerExample write my-instance example-db");
+ System.err.println(" SpannerExample delete my-instance example-db");
+ System.err.println(" SpannerExample query my-instance example-db");
+ System.err.println(" SpannerExample read my-instance example-db");
+ System.err.println(" SpannerExample addmarketingbudget my-instance example-db");
+ System.err.println(" SpannerExample update my-instance example-db");
+ System.err.println(" SpannerExample writetransaction my-instance example-db");
+ System.err.println(" SpannerExample querymarketingbudget my-instance example-db");
+ System.err.println(" SpannerExample addindex my-instance example-db");
+ System.err.println(" SpannerExample readindex my-instance example-db");
+ System.err.println(" SpannerExample queryindex my-instance example-db");
+ System.err.println(" SpannerExample addstoringindex my-instance example-db");
+ System.err.println(" SpannerExample readstoringindex my-instance example-db");
+ System.err.println(" SpannerExample readonlytransaction my-instance example-db");
+ System.err.println(" SpannerExample readstaledata my-instance example-db");
+ System.err.println(" SpannerExample addcommittimestamp my-instance example-db");
+ System.err.println(" SpannerExample updatewithtimestamp my-instance example-db");
+ System.err.println(" SpannerExample querywithtimestamp my-instance example-db");
+ System.err.println(" SpannerExample createtablewithtimestamp my-instance example-db");
+ System.err.println(" SpannerExample writewithtimestamp my-instance example-db");
+ System.err.println(" SpannerExample querysingerstable my-instance example-db");
+ System.err.println(" SpannerExample queryperformancestable my-instance example-db");
+ System.err.println(" SpannerExample writestructdata my-instance example-db");
+ System.err.println(" SpannerExample querywithstruct my-instance example-db");
+ System.err.println(" SpannerExample querywitharrayofstruct my-instance example-db");
+ System.err.println(" SpannerExample querystructfield my-instance example-db");
+ System.err.println(" SpannerExample querynestedstructfield my-instance example-db");
+ System.err.println(" SpannerExample insertusingdml my-instance example-db");
+ System.err.println(" SpannerExample updateusingdml my-instance example-db");
+ System.err.println(" SpannerExample deleteusingdml my-instance example-db");
+ System.err.println(" SpannerExample updateusingdmlwithtimestamp my-instance example-db");
+ System.err.println(" SpannerExample writeandreadusingdml my-instance example-db");
+ System.err.println(" SpannerExample updateusingdmlwithstruct my-instance example-db");
+ System.err.println(" SpannerExample writeusingdml my-instance example-db");
+ System.err.println(" SpannerExample querywithparameter my-instance example-db");
+ System.err.println(" SpannerExample writewithtransactionusingdml my-instance example-db");
+ System.err.println(" SpannerExample updateusingpartitioneddml my-instance example-db");
+ System.err.println(" SpannerExample deleteusingpartitioneddml my-instance example-db");
+ System.err.println(" SpannerExample updateusingbatchdml my-instance example-db");
+ System.err.println(" SpannerExample createtablewithdatatypes my-instance example-db");
+ System.err.println(" SpannerExample writedatatypesdata my-instance example-db");
+ System.err.println(" SpannerExample querywitharray my-instance example-db");
+ System.err.println(" SpannerExample querywithbool my-instance example-db");
+ System.err.println(" SpannerExample querywithbytes my-instance example-db");
+ System.err.println(" SpannerExample querywithdate my-instance example-db");
+ System.err.println(" SpannerExample querywithfloat my-instance example-db");
+ System.err.println(" SpannerExample querywithint my-instance example-db");
+ System.err.println(" SpannerExample querywithstring my-instance example-db");
+ System.err.println(" SpannerExample querywithtimestampparameter my-instance example-db");
+ System.err.println(" SpannerExample clientwithqueryoptions my-instance example-db");
+ System.err.println(" SpannerExample querywithqueryoptions my-instance example-db");
+ System.err.println(" SpannerExample createbackup my-instance example-db");
+ System.err.println(" SpannerExample listbackups my-instance example-db");
+ System.err.println(" SpannerExample listbackupoperations my-instance example-db backup-id");
+ System.err.println(" SpannerExample listdatabaseoperations my-instance example-db");
+ System.err.println(" SpannerExample restorebackup my-instance example-db");
+ System.exit(1);
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 3 && args.length != 4) {
+ printUsageAndExit();
+ }
+ // [START init_client]
+ SpannerOptions options = SpannerOptions.newBuilder().build();
+ Spanner spanner = options.getService();
+ DatabaseAdminClient dbAdminClient = null;
+ try {
+ final String command = args[0];
+ DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]);
+ // [END init_client]
+ // This will return the default project id based on the environment.
+ String clientProject = spanner.getOptions().getProjectId();
+ if (!db.getInstanceId().getProject().equals(clientProject)) {
+ System.err.println(
+ "Invalid project specified. Project in the database id should match the"
+ + "project name set in the environment variable GOOGLE_CLOUD_PROJECT. Expected: "
+ + clientProject);
+ printUsageAndExit();
+ }
+ // Generate a backup id for the sample database.
+ String backupId = null;
+ if (args.length == 4) {
+ backupId = args[3];
+ }
+
+ // [START init_client]
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ dbAdminClient = DatabaseAdminClient.create();
+
+ // Use client here...
+ // [END init_client]
+
+ run(dbClient, dbAdminClient, command, db, backupId);
+ // [START init_client]
+ } finally {
+ if (dbAdminClient != null) {
+ if (!dbAdminClient.isShutdown() || !dbAdminClient.isTerminated()) {
+ dbAdminClient.close();
+ }
+ }
+ spanner.close();
+ }
+ // [END init_client]
+ System.out.println("Closed client");
+ }
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CopyBackupIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CopyBackupIT.java
new file mode 100644
index 00000000000..1d510428880
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CopyBackupIT.java
@@ -0,0 +1,112 @@
+/*
+ * 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.example.spanner.admin.generated;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.example.spanner.SampleRunner;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@Ignore
+public class CopyBackupIT extends SampleTestBaseV2 {
+
+ private static String key;
+
+ @BeforeClass
+ public static void setUp() {
+ String keyLocation = Preconditions
+ .checkNotNull(System.getProperty("spanner.test.key.location"));
+ String keyRing = Preconditions.checkNotNull(System.getProperty("spanner.test.key.ring"));
+ String keyName = Preconditions.checkNotNull(System.getProperty("spanner.test.key.name"));
+ key = "projects/" + projectId + "/locations/" + keyLocation + "/keyRings/" + keyRing
+ + "/cryptoKeys/" + keyName;
+ }
+
+ @Test
+ public void testEncryptedDatabaseAndBackupAndRestore() throws Exception {
+ final String databaseId = idGenerator.generateDatabaseId();
+ final String sourceBackupId = idGenerator.generateBackupId();
+ final String destinationBackupId = idGenerator.generateBackupId();
+
+ String out = SampleRunner.runSample(() ->
+ SpannerSample.createDatabase(
+ databaseAdminClient, InstanceName.of(projectId, instanceId), databaseId));
+ assertThat(out).contains(String.format(
+ "Created database [%s]", DatabaseName.of(projectId, instanceId, databaseId)));
+
+ out = SampleRunner.runSampleWithRetry(() ->
+ CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(
+ databaseAdminClient, projectId, instanceId, databaseId, sourceBackupId, key
+ ), new ShouldRetryBackupOperation());
+ assertThat(out).containsMatch(
+ "Backup projects/" + projectId + "/instances/" + instanceId + "/backups/"
+ + sourceBackupId + " of size \\d+ bytes was created at (.*) using encryption key "
+ + key);
+
+ out = SampleRunner.runSampleWithRetry(() ->
+ CopyBackupSample.copyBackup(
+ databaseAdminClient, projectId, instanceId, sourceBackupId, destinationBackupId
+ ), new ShouldRetryBackupOperation());
+
+ assertThat(out).contains("Copied backup [" + BackupName.of(
+ projectId, instanceId, destinationBackupId).toString() + "]");
+ assertThat(out).containsMatch(String.format(
+ "Backup projects/%s/instances/%s/backups/%s of size \\d+ bytes was copied at (.*)",
+ projectId, instanceId, destinationBackupId, key));
+ }
+
+ static class ShouldRetryBackupOperation implements Predicate {
+
+ private static final int MAX_ATTEMPTS = 20;
+ private int attempts = 0;
+
+ @Override
+ public boolean test(SpannerException e) {
+ if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION
+ && e.getMessage().contains("Please retry the operation once the pending")) {
+ attempts++;
+ if (attempts == MAX_ATTEMPTS) {
+ // Throw custom exception so it is easier to locate in the log why it went wrong.
+ throw SpannerExceptionFactory.newSpannerException(ErrorCode.DEADLINE_EXCEEDED,
+ String.format("Operation failed %d times because of other pending operations. "
+ + "Giving up operation.\n", attempts),
+ e);
+ }
+ // Wait one minute before retrying.
+ Uninterruptibles.sleepUninterruptibly(60L, TimeUnit.SECONDS);
+ return true;
+ }
+ return false;
+ }
+ }
+}
+
diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/EncryptionKeyIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/EncryptionKeyIT.java
new file mode 100644
index 00000000000..5b2513d91c8
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/EncryptionKeyIT.java
@@ -0,0 +1,123 @@
+/*
+ * 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.admin.generated;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.example.spanner.SampleRunner;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for: {@link com.example.spanner.CreateDatabaseWithEncryptionKey},
+ * {@link com.example.spanner.CreateBackupWithEncryptionKey} and
+ * {@link com.example.spanner.RestoreBackupWithEncryptionKey}
+ */
+@RunWith(JUnit4.class)
+@Ignore
+public class EncryptionKeyIT extends SampleTestBaseV2 {
+
+ private static String key;
+
+ @BeforeClass
+ public static void setUp() {
+ String keyLocation = Preconditions
+ .checkNotNull(System.getProperty("spanner.test.key.location"));
+ String keyRing = Preconditions.checkNotNull(System.getProperty("spanner.test.key.ring"));
+ String keyName = Preconditions.checkNotNull(System.getProperty("spanner.test.key.name"));
+ key = "projects/" + projectId + "/locations/" + keyLocation + "/keyRings/" + keyRing
+ + "/cryptoKeys/" + keyName;
+ }
+
+ @Test
+ public void testEncryptedDatabaseAndBackupAndRestore() throws Exception {
+ final String databaseId = idGenerator.generateDatabaseId();
+ final String backupId = idGenerator.generateBackupId();
+ final String restoreId = idGenerator.generateDatabaseId();
+
+ String out = SampleRunner.runSample(() ->
+ SpannerSample.createDatabase(
+ databaseAdminClient, InstanceName.of(projectId, instanceId), databaseId));
+ assertThat(out).contains(String.format(
+ "Created database [%s]", DatabaseName.of(projectId, instanceId, databaseId)));
+
+ out = SampleRunner.runSampleWithRetry(() ->
+ CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(
+ databaseAdminClient,
+ projectId,
+ instanceId,
+ databaseId,
+ backupId,
+ key
+ ), new ShouldRetryBackupOperation());
+ assertThat(out).containsMatch(
+ "Backup projects/" + projectId + "/instances/" + instanceId + "/backups/" + backupId
+ + " of size \\d+ bytes was created at (.*) using encryption key " + key);
+
+ out = SampleRunner.runSampleWithRetry(() ->
+ RestoreBackupWithEncryptionKey.restoreBackupWithEncryptionKey(
+ databaseAdminClient,
+ projectId,
+ instanceId,
+ backupId,
+ restoreId,
+ key
+ ), new ShouldRetryBackupOperation());
+ assertThat(out).contains(
+ "Database projects/" + projectId + "/instances/" + instanceId + "/databases/" + databaseId
+ + " restored to projects/" + projectId + "/instances/" + instanceId + "/databases/"
+ + restoreId + " from backup projects/" + projectId + "/instances/" + instanceId
+ + "/backups/" + backupId + " using encryption key " + key);
+ }
+
+ static class ShouldRetryBackupOperation implements Predicate {
+
+ private static final int MAX_ATTEMPTS = 20;
+ private int attempts = 0;
+
+ @Override
+ public boolean test(SpannerException e) {
+ if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION
+ && e.getMessage().contains("Please retry the operation once the pending")) {
+ attempts++;
+ if (attempts == MAX_ATTEMPTS) {
+ // Throw custom exception so it is easier to locate in the log why it went wrong.
+ throw SpannerExceptionFactory.newSpannerException(ErrorCode.DEADLINE_EXCEEDED,
+ String.format("Operation failed %d times because of other pending operations. "
+ + "Giving up operation.\n", attempts),
+ e);
+ }
+ // Wait one minute before retrying.
+ Uninterruptibles.sleepUninterruptibly(60L, TimeUnit.SECONDS);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgSpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgSpannerSampleIT.java
new file mode 100644
index 00000000000..90885b71ce8
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgSpannerSampleIT.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.spanner.admin.generated;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
+import com.google.spanner.admin.database.v1.Database;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@code PgSpannerSample}
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("checkstyle:abbreviationaswordinname")
+public class PgSpannerSampleIT extends SampleTestBaseV2 {
+
+ private static final int DBID_LENGTH = 20;
+ // The instance needs to exist for tests to pass.
+ private static final String instanceId = System.getProperty("spanner.test.instance");
+ private static final String baseDbId = System.getProperty("spanner.sample.database");
+ static Spanner spanner;
+ static DatabaseId dbId;
+ static DatabaseAdminClient dbClient;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ SpannerOptions options =
+ SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build();
+ spanner = options.getService();
+ dbClient = DatabaseAdminClient.create();
+ dbId = DatabaseId.of(options.getProjectId(), instanceId, idGenerator.generateDatabaseId());
+ // Delete stale test databases that have been created earlier by this test, but not deleted.
+ deleteStaleTestDatabases();
+ }
+
+ static void deleteStaleTestDatabases() {
+ Timestamp now = Timestamp.now();
+ Pattern samplePattern = getTestDbIdPattern(PgSpannerSampleIT.baseDbId);
+ Pattern restoredPattern = getTestDbIdPattern("restored");
+ for (Database db : dbClient.listDatabases(
+ InstanceName.of(projectId, instanceId)).iterateAll()) {
+ DatabaseName databaseName = DatabaseName.parse(db.getName());
+ if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(),
+ TimeUnit.SECONDS) > 24) {
+ if (databaseName.getDatabase().length() >= DBID_LENGTH) {
+ if (samplePattern.matcher(
+ toComparableId(PgSpannerSampleIT.baseDbId, databaseName.getDatabase())).matches()) {
+ dbClient.dropDatabase(db.getName());
+ }
+ if (restoredPattern.matcher(toComparableId("restored", databaseName.getDatabase()))
+ .matches()) {
+ dbClient.dropDatabase(db.getName());
+ }
+ }
+ }
+ }
+ }
+
+ private static String toComparableId(String baseId, String existingId) {
+ String zeroUuid = "00000000-0000-0000-0000-0000-00000000";
+ int shouldBeLength = (baseId + "-" + zeroUuid).length();
+ int missingLength = shouldBeLength - existingId.length();
+ return existingId + zeroUuid.substring(zeroUuid.length() - missingLength);
+ }
+
+ private static Pattern getTestDbIdPattern(String baseDbId) {
+ return Pattern.compile(
+ baseDbId + "-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{8}",
+ Pattern.CASE_INSENSITIVE);
+ }
+
+ private String runSample(String command) throws Exception {
+ final PrintStream stdOut = System.out;
+ final ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ final PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ System.out.println(instanceId + ":" + dbId.getDatabase());
+ PgSpannerSample.main(new String[]{command, instanceId, dbId.getDatabase()});
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ @Test
+ public void testSample() throws Exception {
+ assertThat(instanceId).isNotNull();
+ assertThat(dbId.getDatabase()).isNotNull();
+
+ System.out.println("Create Database ...");
+ String out = runSample("createpgdatabase");
+ assertThat(out).contains("Created database");
+ assertThat(out).contains(dbId.getName());
+
+ System.out.println("Create sample tables Singers and Albums ...");
+ runSample("createtableusingddl");
+
+ System.out.println("Write data to sample tables ...");
+ runSample("write");
+
+ System.out.println("Read data from sample tables ...");
+ out = runSample("read");
+ assertThat(out).contains("1 1 Total Junk");
+
+ System.out.println("Write data using DML to sample table ...");
+ runSample("writeusingdml");
+ System.out.println("Query Singers table ...");
+ out = runSample("querysingerstable");
+ assertThat(out).contains("Melissa Garcia");
+ out = runSample("query");
+ assertThat(out).contains("1 1 Total Junk");
+ out = runSample("querywithparameter");
+ assertThat(out).contains("12 Melissa Garcia");
+
+ System.out.println("Add column marketing budget ...");
+ runSample("addmarketingbudget");
+
+ // wait for 15 seconds to elapse and then run an update, and query for stale data
+ long lastUpdateDataTimeInMillis = System.currentTimeMillis();
+ while (System.currentTimeMillis() < lastUpdateDataTimeInMillis + 16000) {
+ Thread.sleep(1000);
+ }
+ System.out.println("Write data to marketing budget ...");
+ runSample("update");
+
+ System.out.println("Query marketing budget ...");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 100000");
+ assertThat(out).contains("2 2 500000");
+
+ System.out.println("Write with transaction using dml...");
+ runSample("writewithtransactionusingdml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 300000");
+ assertThat(out).contains("1 1 300000");
+
+ System.out.println("Add index ...");
+ runSample("addindex");
+
+ System.out.println("Read index ...");
+ out = runSample("readindex");
+ assertThat(out).contains("Go, Go, Go");
+ assertThat(out).contains("Forever Hold Your Peace");
+ assertThat(out).contains("Green");
+
+ System.out.println("Add Storing index ...");
+ runSample("addstoringindex");
+
+ System.out.println("Read storing index ...");
+ out = runSample("readstoringindex");
+ assertThat(out).contains("300000");
+
+ System.out.println("Read only transaction ...");
+ out = runSample("readonlytransaction");
+ assertThat(out.replaceAll("[\r\n]+", " "))
+ .containsMatch("(Total Junk.*){2}");
+
+ System.out.println("Add Timestamp column ...");
+ out = runSample("addlastupdatetimestampcolumn");
+ assertThat(out).contains("Added LastUpdateTime as a timestamp column");
+
+ System.out.println("Update values in Timestamp column ...");
+ runSample("updatewithtimestamp");
+ out = runSample("querywithtimestamp");
+ assertThat(out).contains("1 1 1000000");
+ assertThat(out).contains("2 2 750000");
+
+ System.out.println("Create table with Timestamp column ...");
+ out = runSample("createtablewithtimestamp");
+ assertThat(out).contains("Created Performances table in database");
+
+ System.out.println("Write with Timestamp ...");
+ runSample("writewithtimestamp");
+ out = runSample("queryperformancestable");
+ assertThat(out).contains("1 4 11000");
+ assertThat(out).contains("1 19 15000");
+ assertThat(out).contains("2 42 7000");
+
+ System.out.println("Write using DML ...");
+ runSample("insertusingdml");
+ out = runSample("querysingerstable");
+ assertThat(out).contains("Virginia Watson");
+
+ System.out.println("Update using DML ...");
+ runSample("updateusingdml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 2000000");
+
+ System.out.println("Delete using DML ...");
+ runSample("deleteusingdml");
+ out = runSample("querysingerstable");
+ assertThat(out).doesNotContain("Alice Trentor");
+
+ System.out.println("Write and Read using DML ...");
+ out = runSample("writeandreadusingdml");
+ assertThat(out).contains("Timothy Campbell");
+
+ System.out.println("Update using partitioned DML ...");
+ runSample("updateusingpartitioneddml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("2 2 100000");
+ assertThat(out).contains("1 1 2000000");
+
+ System.out.println("Delete using Partitioned DML ...");
+ runSample("deleteusingpartitioneddml");
+ out = runSample("querysingerstable");
+ assertThat(out).doesNotContain("Timothy Grant");
+ assertThat(out).doesNotContain("Melissa Garcia");
+ assertThat(out).doesNotContain("Russell Morales");
+ assertThat(out).doesNotContain("Jacqueline Long");
+ assertThat(out).doesNotContain("Dylan Shaw");
+
+ System.out.println("Update in Batch using DML ...");
+ out = runSample("updateusingbatchdml");
+ assertThat(out).contains("1 record updated by stmt 0");
+ assertThat(out).contains("1 record updated by stmt 1");
+
+ System.out.println("Create table with data types ...");
+ out = runSample("createtablewithdatatypes");
+ assertThat(out).contains("Created Venues table in database");
+
+ System.out.println("Write into table and Query Boolean Type ...");
+ runSample("writedatatypesdata");
+ out = runSample("querywithbool");
+ assertThat(out).contains("19 Venue 19 true");
+
+ System.out.println("Query with Bytes ...");
+ out = runSample("querywithbytes");
+ assertThat(out).contains("4 Venue 4");
+
+ System.out.println("Query with Float ...");
+ out = runSample("querywithfloat");
+ assertThat(out).contains("4 Venue 4 0.8");
+ assertThat(out).contains("19 Venue 19 0.9");
+
+ System.out.println("Query with Int ...");
+ out = runSample("querywithint");
+ assertThat(out).contains("19 Venue 19 6300");
+ assertThat(out).contains("42 Venue 42 3000");
+
+ System.out.println("Query with String ...");
+ out = runSample("querywithstring");
+ assertThat(out).contains("42 Venue 42");
+
+ System.out.println("Query with Timestamp parameter ...");
+ out = runSample("querywithtimestampparameter");
+ assertThat(out).contains("4 Venue 4");
+ assertThat(out).contains("19 Venue 19");
+ assertThat(out).contains("42 Venue 42");
+
+ System.out.println("Query with Numeric Type ...");
+ out = runSample("querywithnumeric");
+ assertThat(out).contains("19 Venue 19 1200100");
+ assertThat(out).contains("42 Venue 42 390650.99");
+
+ System.out.println("Query options ...");
+ out = runSample("clientwithqueryoptions");
+ assertThat(out).contains("1 1 Total Junk");
+ out = runSample("querywithqueryoptions");
+ assertThat(out).contains("1 1 Total Junk");
+ }
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/SpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/SpannerSampleIT.java
new file mode 100644
index 00000000000..cf719ff9e43
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/SpannerSampleIT.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.spanner.admin.generated;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.example.spanner.SampleRunner;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.Instance;
+import com.google.cloud.spanner.InstanceAdminClient;
+import com.google.cloud.spanner.InstanceConfigId;
+import com.google.cloud.spanner.InstanceId;
+import com.google.cloud.spanner.InstanceInfo;
+import com.google.cloud.spanner.Options;
+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.admin.database.v1.DatabaseAdminClient;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.spanner.admin.database.v1.Backup;
+import com.google.spanner.admin.database.v1.BackupName;
+import com.google.spanner.admin.database.v1.Database;
+import com.google.spanner.admin.database.v1.DatabaseName;
+import com.google.spanner.admin.database.v1.InstanceName;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@code SpannerSample}
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("checkstyle:abbreviationaswordinname")
+public class SpannerSampleIT extends SampleTestBaseV2 {
+
+ private static final int DBID_LENGTH = 20;
+ // The instance needs to exist for tests to pass.
+ private static final String instanceId = System.getProperty("spanner.test.instance");
+ private static final String baseDbId = System.getProperty("spanner.sample.database");
+ private static final String keyLocation =
+ Preconditions.checkNotNull(System.getProperty("spanner.test.key.location"));
+ private static final String keyRing =
+ Preconditions.checkNotNull(System.getProperty("spanner.test.key.ring"));
+ private static final String keyName =
+ Preconditions.checkNotNull(System.getProperty("spanner.test.key.name"));
+ private static final String encryptedBackupId = formatForTest(baseDbId);
+ private static final long STALE_INSTANCE_THRESHOLD_SECS =
+ TimeUnit.SECONDS.convert(24L, TimeUnit.HOURS);
+ static Spanner spanner;
+ static DatabaseAdminClient databaseAdminClient;
+ private static String key;
+ private long lastUpdateDataTimeInMillis;
+
+ private String runSample(String command, String databaseId) throws Exception {
+ PrintStream stdOut = System.out;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ SpannerSample.main(new String[]{command, instanceId, databaseId, null});
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ private String runSample(String command, String databaseId, String backupId) throws Exception {
+ PrintStream stdOut = System.out;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ SpannerSample.main(new String[]{command, instanceId, databaseId, backupId});
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ SpannerOptions options =
+ SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build();
+ spanner = options.getService();
+ databaseAdminClient = DatabaseAdminClient.create();
+ // Delete stale test databases that have been created earlier by this test, but not deleted.
+ deleteStaleTestDatabases();
+ key =
+ String.format(
+ "projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s",
+ options.getProjectId(), keyLocation, keyRing, keyName);
+
+ /*
+ * Delete stale instances that have been created earlier by this test but not deleted.
+ * Backups needed to be deleted from the instance first, as the instance can only be
+ * deleted once all backups have been deleted.
+ * */
+ deleteStaleEncryptedTestInstances();
+ }
+
+ /**
+ * Deleting all the test instances with name starting with 'encrypted-test-' and were created
+ * before 24 hours.
+ *
+ * @throws InterruptedException If Thread.sleep() interrupted
+ */
+ private static void deleteStaleEncryptedTestInstances() throws InterruptedException {
+ Timestamp now = Timestamp.now();
+
+ for (Instance instance :
+ spanner
+ .getInstanceAdminClient()
+ .listInstances(Options.filter("name:encrypted-test-"))
+ .iterateAll()) {
+ if ((now.getSeconds() - instance.getCreateTime().getSeconds())
+ > STALE_INSTANCE_THRESHOLD_SECS) {
+ deleteAllBackups(instance.getId().getInstance());
+ instance.delete();
+ }
+ }
+ }
+
+ static void deleteStaleTestDatabases() throws IOException {
+ Timestamp now = Timestamp.now();
+ Pattern samplePattern = getTestDbIdPattern(SpannerSampleIT.baseDbId);
+ Pattern restoredPattern = getTestDbIdPattern("restored");
+ try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+ for (Database db : databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId))
+ .iterateAll()) {
+ DatabaseName databaseName = DatabaseName.parse(db.getName());
+ if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(),
+ TimeUnit.SECONDS) > 24) {
+ if (databaseName.getDatabase().length() >= DBID_LENGTH) {
+ if (samplePattern.matcher(
+ toComparableId(SpannerSampleIT.baseDbId, databaseName.getDatabase())).matches()) {
+ databaseAdminClient.dropDatabase(db.getName());
+ }
+ if (restoredPattern.matcher(toComparableId("restored", databaseName.getDatabase()))
+ .matches()) {
+ databaseAdminClient.dropDatabase(db.getName());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ databaseAdminClient.deleteBackup(BackupName.of(projectId, instanceId, encryptedBackupId));
+ spanner.close();
+ }
+
+ @Test
+ public void testSample() throws Exception {
+ String databaseId = idGenerator.generateDatabaseId();
+ DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
+ assertThat(instanceId).isNotNull();
+ assertThat(databaseId).isNotNull();
+ String out = runSample("createdatabase", databaseId);
+ assertThat(out).contains("Created database");
+ assertThat(out).contains(dbId.getName());
+
+ System.out.println("Write data to sample tables ...");
+ runSample("write", databaseId);
+
+ System.out.println("Delete data to sample tables ...");
+ out = runSample("delete", databaseId);
+ assertThat(out).contains("Records deleted.");
+
+ runSample("write", databaseId);
+
+ System.out.println("Read data from sample tables ...");
+ out = runSample("read", databaseId);
+ assertThat(out).contains("1 1 Total Junk");
+
+ out = runSample("query", databaseId);
+ assertThat(out).contains("1 1 Total Junk");
+ runSample("addmarketingbudget", databaseId);
+
+ // wait for 15 seconds to elapse and then run an update, and query for stale data
+ lastUpdateDataTimeInMillis = System.currentTimeMillis();
+ while (System.currentTimeMillis() < lastUpdateDataTimeInMillis + 16000) {
+ Thread.sleep(1000);
+ }
+ runSample("update", databaseId);
+
+ System.out.println("Read stale data from sample tables ...");
+ out = runSample("readstaledata", databaseId);
+ assertThat(out).contains("1 1 NULL");
+ runSample("writetransaction", databaseId);
+
+ System.out.println("Query marketing budget ...");
+ out = runSample("querymarketingbudget", databaseId);
+ assertThat(out).contains("1 1 300000");
+ assertThat(out).contains("2 2 300000");
+
+ System.out.println("Add index ...");
+ runSample("addindex", databaseId);
+
+ System.out.println("Query index ...");
+ out = runSample("queryindex", databaseId);
+ assertThat(out).contains("Go, Go, Go");
+ assertThat(out).contains("Forever Hold Your Peace");
+ assertThat(out).doesNotContain("Green");
+
+ System.out.println("Read index ...");
+ out = runSample("readindex", databaseId);
+ assertThat(out).contains("Go, Go, Go");
+ assertThat(out).contains("Forever Hold Your Peace");
+ assertThat(out).contains("Green");
+
+ System.out.println("Add Storing index ...");
+ runSample("addstoringindex", databaseId);
+ out = runSample("readstoringindex", databaseId);
+ assertThat(out).contains("300000");
+
+ System.out.println("Read storing index ...");
+ out = runSample("readonlytransaction", databaseId);
+ assertThat(out.replaceAll("[\r\n]+", " ")).containsMatch("(Total Junk.*){2}");
+
+ out = runSample("addcommittimestamp", databaseId);
+ assertThat(out).contains("Added LastUpdateTime as a commit timestamp column");
+
+ runSample("updatewithtimestamp", databaseId);
+ out = runSample("querywithtimestamp", databaseId);
+ assertThat(out).contains("1 1 1000000");
+ assertThat(out).contains("2 2 750000");
+
+ out = runSample("createtablewithtimestamp", databaseId);
+ assertThat(out).contains("Created Performances table in database");
+
+ runSample("writewithtimestamp", databaseId);
+ out = runSample("queryperformancestable", databaseId);
+ assertThat(out).contains("1 4 2017-10-05 11000");
+ assertThat(out).contains("1 19 2017-11-02 15000");
+ assertThat(out).contains("2 42 2017-12-23 7000");
+
+ runSample("writestructdata", databaseId);
+ out = runSample("querywithstruct", databaseId);
+ assertThat(out).startsWith("6\n");
+
+ out = runSample("querywitharrayofstruct", databaseId);
+ assertThat(out).startsWith("8\n7\n6");
+
+ out = runSample("querystructfield", databaseId);
+ assertThat(out).startsWith("6\n");
+
+ out = runSample("querynestedstructfield", databaseId);
+ assertThat(out).contains("6 Imagination\n");
+ assertThat(out).contains("9 Imagination\n");
+
+ runSample("insertusingdml", databaseId);
+ out = runSample("querysingerstable", databaseId);
+ assertThat(out).contains("Virginia Watson");
+
+ runSample("updateusingdml", databaseId);
+ out = runSample("querymarketingbudget", databaseId);
+ assertThat(out).contains("1 1 2000000");
+
+ runSample("deleteusingdml", databaseId);
+ out = runSample("querysingerstable", databaseId);
+ assertThat(out).doesNotContain("Alice Trentor");
+
+ out = runSample("updateusingdmlwithtimestamp", databaseId);
+ assertThat(out).contains("2 records updated");
+
+ out = runSample("writeandreadusingdml", databaseId);
+ assertThat(out).contains("Timothy Campbell");
+
+ runSample("updateusingdmlwithstruct", databaseId);
+ out = runSample("querysingerstable", databaseId);
+ assertThat(out).contains("Timothy Grant");
+
+ runSample("writeusingdml", databaseId);
+ out = runSample("querysingerstable", databaseId);
+ assertThat(out).contains("Melissa Garcia");
+ assertThat(out).contains("Russell Morales");
+ assertThat(out).contains("Jacqueline Long");
+ assertThat(out).contains("Dylan Shaw");
+ out = runSample("querywithparameter", databaseId);
+ assertThat(out).contains("12 Melissa Garcia");
+
+ runSample("writewithtransactionusingdml", databaseId);
+ out = runSample("querymarketingbudget", databaseId);
+ assertThat(out).contains("1 1 2200000");
+ assertThat(out).contains("2 2 550000");
+
+ runSample("updateusingpartitioneddml", databaseId);
+ out = runSample("querymarketingbudget", databaseId);
+ assertThat(out).contains("1 1 2200000");
+ assertThat(out).contains("2 2 100000");
+
+ runSample("deleteusingpartitioneddml", databaseId);
+ out = runSample("querysingerstable", databaseId);
+ assertThat(out).doesNotContain("Timothy Grant");
+ assertThat(out).doesNotContain("Melissa Garcia");
+ assertThat(out).doesNotContain("Russell Morales");
+ assertThat(out).doesNotContain("Jacqueline Long");
+ assertThat(out).doesNotContain("Dylan Shaw");
+
+ out = runSample("updateusingbatchdml", databaseId);
+ assertThat(out).contains("1 record updated by stmt 0");
+ assertThat(out).contains("1 record updated by stmt 1");
+
+ out = runSample("createtablewithdatatypes", databaseId);
+ assertThat(out).contains("Created Venues table in database");
+
+ runSample("writedatatypesdata", databaseId);
+ out = runSample("querywitharray", databaseId);
+ assertThat(out).contains("19 Venue 19 2020-11-01");
+ assertThat(out).contains("42 Venue 42 2020-10-01");
+
+ out = runSample("querywithbool", databaseId);
+ assertThat(out).contains("19 Venue 19 true");
+
+ out = runSample("querywithbytes", databaseId);
+ assertThat(out).contains("4 Venue 4");
+
+ out = runSample("querywithdate", databaseId);
+ assertThat(out).contains("4 Venue 4 2018-09-02");
+ assertThat(out).contains("42 Venue 42 2018-10-01");
+
+ out = runSample("querywithfloat", databaseId);
+ assertThat(out).contains("4 Venue 4 0.8");
+ assertThat(out).contains("19 Venue 19 0.9");
+
+ out = runSample("querywithint", databaseId);
+ assertThat(out).contains("19 Venue 19 6300");
+ assertThat(out).contains("42 Venue 42 3000");
+
+ out = runSample("querywithstring", databaseId);
+ assertThat(out).contains("42 Venue 42");
+
+ out = runSample("querywithtimestampparameter", databaseId);
+ assertThat(out).contains("4 Venue 4");
+ assertThat(out).contains("19 Venue 19");
+ assertThat(out).contains("42 Venue 42");
+
+ out = runSample("querywithnumeric", databaseId);
+ assertThat(out).contains("19 Venue 19 1200100");
+ assertThat(out).contains("42 Venue 42 390650.99");
+
+ out = runSample("clientwithqueryoptions", databaseId);
+ assertThat(out).contains("1 1 Total Junk");
+ out = runSample("querywithqueryoptions", databaseId);
+ assertThat(out).contains("1 1 Total Junk");
+ }
+
+ @Test
+ public void testBackupSamples_withoutEncryption() {
+ String databaseId = idGenerator.generateDatabaseId();
+ DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
+ String restoreDatabaseId = idGenerator.generateDatabaseId();
+ String backupId = idGenerator.generateBackupId();
+
+ try {
+ assertThat(instanceId).isNotNull();
+ assertThat(databaseId).isNotNull();
+
+ System.out.println("Creating Database ...");
+ String out = runSample("createdatabase", databaseId);
+ assertThat(out).contains("Created database");
+ assertThat(out).contains(dbId.getName());
+
+ BackupName backupName = BackupName.of(projectId, instanceId, backupId);
+
+ System.out.println("Creating Backup ...");
+ out = runSample("createbackup", databaseId, backupId);
+ assertThat(out).contains("Created backup [" + backupName.toString() + "]");
+
+ // TODO: remove try-catch when filtering on metadata fields works.
+ try {
+ System.out.println("List Backup Operations ...");
+ out = runSample("listbackupoperations", databaseId, backupId);
+ assertThat(out).contains(
+ String.format(
+ "Backup %s on database %s pending:", backupName, dbId.getName()));
+ assertTrue("Out does not contain copy backup operations", out.contains(
+ "Copy Backup Operations"));
+ } catch (SpannerException e) {
+ assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT);
+ assertThat(e.getMessage()).contains("Cannot evaluate filter expression");
+ }
+
+ System.out.println("List Backup ...");
+ out = runSample("listbackups", databaseId, backupId);
+ assertThat(out).contains("All backups:");
+ assertThat(out).contains(
+ String.format("All backups with backup name containing \"%s\":", backupId));
+ assertThat(out).contains(String.format(
+ "All backups for databases with a name containing \"%s\":",
+ dbId.getDatabase()));
+ assertThat(out).contains(
+ String.format("All backups that expire before"));
+ assertThat(out).contains("All backups with size greater than 100 bytes:");
+ assertThat(out).containsMatch(
+ Pattern.compile("All databases created after (.+) and that are ready:"));
+ assertThat(out).contains("All backups, listed using pagination:");
+ // All the above tests should include the created backup exactly once, i.e. exactly 6 times.
+ assertThat(countOccurrences(out, backupName.toString())).isEqualTo(6);
+
+ // Try the restore operation in a retry loop, as there is a limit on the number of restore
+ // operations that is allowed to execute simultaneously, and we should retry if we hit this
+ // limit.
+ boolean restored = false;
+ int restoreAttempts = 0;
+ while (true) {
+ try {
+ System.out.println("Restore Backup ...");
+ out = runSample("restorebackup", restoreDatabaseId, backupId);
+ assertThat(out).contains(
+ "Restored database ["
+ + DatabaseName.of(projectId, instanceId, restoreDatabaseId).toString()
+ + "] from ["
+ + backupName
+ + "]");
+ restored = true;
+ break;
+ } catch (SpannerException e) {
+ if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION
+ && e.getMessage()
+ .contains("Please retry the operation once the pending restores complete")) {
+ restoreAttempts++;
+ if (restoreAttempts == 10) {
+ System.out.println(
+ "Restore operation failed 10 times because of other pending restores. "
+ + "Giving up restore.");
+ break;
+ }
+ Uninterruptibles.sleepUninterruptibly(60L, TimeUnit.SECONDS);
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ if (restored) {
+ System.out.println("List Database Operations ...");
+ out = runSample("listdatabaseoperations", restoreDatabaseId);
+ assertThat(out).contains(
+ String.format(
+ "Database %s restored from backup",
+ DatabaseId.of(dbId.getInstanceId(), restoreDatabaseId).getName()));
+ }
+
+ System.out.println("Updating backup ...");
+ out = runSample("updatebackup", databaseId, backupId);
+ assertThat(out).contains(
+ String.format("Updated backup [" + backupId + "]"));
+
+ // Drop the restored database before we try to delete the backup.
+ // Otherwise the delete backup operation might fail as the backup is still in use by
+ // the OptimizeRestoredDatabase operation.
+ databaseAdminClient.dropDatabase(DatabaseName.of(projectId,
+ dbId.getInstanceId().getInstance(), restoreDatabaseId));
+
+ System.out.println("Deleting Backup ...");
+ out = runSample("deletebackup", databaseId, backupId);
+ assertThat(out).contains("Deleted backup [" + backupId + "]");
+
+ } catch (Exception ex) {
+ Assert.fail("Exception raised => " + ex.getCause());
+ }
+ }
+
+ @Test
+ public void testCancelBackupSamples() {
+ String databaseId = idGenerator.generateDatabaseId();
+ DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
+
+ try {
+ assertThat(instanceId).isNotNull();
+ assertThat(databaseId).isNotNull();
+
+ String out = runSample("createdatabase", databaseId);
+ assertThat(out).contains("Created database");
+ assertThat(out).contains(dbId.getName());
+
+ String backupId = idGenerator.generateBackupId();
+
+ out = runSample("cancelcreatebackup", databaseId, backupId);
+ assertThat(out).contains(
+ "Backup operation for [" + backupId + "_cancel] successfully");
+ } catch (Exception ex) {
+ Assert.fail("Exception raised => " + ex.getCause());
+ }
+ }
+
+ @Test
+ public void testEncryptedDatabaseAndBackupSamples() throws Exception {
+ String projectId = spanner.getOptions().getProjectId();
+ String databaseId = idGenerator.generateDatabaseId();
+ String restoreId = idGenerator.generateDatabaseId();
+ // Create a separate instance for this test to prevent multiple parallel backup operations on
+ // the same instance that need to wait for each other.
+ String instanceId = idGenerator.generateInstanceId();
+ InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient();
+ instanceAdminClient
+ .createInstance(InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId))
+ .setDisplayName("Encrypted test instance")
+ .setInstanceConfigId(InstanceConfigId.of(projectId, "regional-" + keyLocation))
+ .setNodeCount(1).build())
+ .get();
+ try {
+ String out = SampleRunner
+ .runSample(() -> SpannerSample.createDatabase(
+ databaseAdminClient, InstanceName.of(projectId, instanceId), databaseId));
+ assertThat(out).contains(String.format(
+ "Created database [%s]", DatabaseName.of(projectId, instanceId, databaseId)));
+
+ out = SampleRunner.runSampleWithRetry(
+ () -> CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(databaseAdminClient,
+ projectId,
+ instanceId, databaseId, encryptedBackupId, key),
+ new ShouldRetryBackupOperation());
+ assertThat(out).containsMatch(String.format(
+ "Backup projects/%s/instances/%s/backups/%s of size \\d+ bytes "
+ + "was created at (.*) using encryption key %s",
+ projectId, instanceId, encryptedBackupId, key));
+
+ out = SampleRunner.runSampleWithRetry(
+ () -> RestoreBackupWithEncryptionKey.restoreBackupWithEncryptionKey(databaseAdminClient,
+ projectId, instanceId, encryptedBackupId, restoreId, key),
+ new ShouldRetryBackupOperation());
+ assertThat(out).contains(String.format(
+ "Database projects/%s/instances/%s/databases/%s"
+ + " restored to projects/%s/instances/%s/databases/%s"
+ + " from backup projects/%s/instances/%s/backups/%s" + " using encryption key %s",
+ projectId, instanceId, databaseId, projectId, instanceId, restoreId,
+ projectId, instanceId, encryptedBackupId, key));
+ } finally {
+ // Delete the backups from the test instance first, as the instance can only be deleted once
+ // all backups have been deleted.
+ deleteAllBackups(instanceId);
+ instanceAdminClient.deleteInstance(instanceId);
+ }
+ }
+
+ @Test
+ public void testDeleteBackups() {
+ try {
+ String projectId = spanner.getOptions().getProjectId();
+ String databaseId = idGenerator.generateDatabaseId();
+ String backupId = idGenerator.generateBackupId();
+
+ String out = SampleRunner
+ .runSample(() -> SpannerSample.createDatabase(
+ databaseAdminClient, InstanceName.of(projectId, instanceId), databaseId));
+ assertThat(out).contains(String.format(
+ "Created database [%s]", DatabaseName.of(projectId, instanceId, databaseId)));
+
+ out = SampleRunner.runSampleWithRetry(
+ () -> CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(databaseAdminClient,
+ projectId, instanceId, databaseId, backupId, key),
+ new ShouldRetryBackupOperation());
+ assertThat(out).containsMatch(String.format(
+ "Backup projects/%s/instances/%s/backups/%s of size \\d+ bytes "
+ + "was created at (.*) using encryption key %s",
+ projectId, instanceId, backupId, key));
+
+ out = runSample("deletebackup", databaseId, backupId);
+ assertThat(out).contains("Deleted backup [" + backupId + "]");
+ } catch (Exception ex) {
+ Assert.fail("Exception raised => " + ex.getCause());
+ }
+ }
+
+ private static void deleteAllBackups(String instanceId) throws InterruptedException {
+ InstanceName instanceName = InstanceName.of(projectId, instanceId);
+ for (Backup backup : databaseAdminClient.listBackups(instanceName.toString()).iterateAll()) {
+ int attempts = 0;
+ while (attempts < 30) {
+ try {
+ attempts++;
+ databaseAdminClient.deleteBackup(backup.getName());
+ break;
+ } catch (SpannerException e) {
+ if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION && e.getMessage()
+ .contains("Please try deleting the backup once the restore or post-restore optimize "
+ + "operations have completed on these databases.")) {
+ // Wait 30 seconds and then retry.
+ Thread.sleep(30_000L);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ private String runSampleRunnable(Runnable sample) {
+ PrintStream stdOut = System.out;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ sample.run();
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ @Test
+ public void testCreateInstanceSample() {
+ String databaseId = idGenerator.generateDatabaseId();
+ DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
+
+ String instanceId = formatForTest("sample-inst");
+ String out =
+ runSampleRunnable(() -> {
+ try {
+ CreateInstanceExample.createInstance(
+ dbId.getInstanceId().getProject(), instanceId);
+ } catch (IOException ex) {
+ System.out.println("Got exception => " + ex);
+ } finally {
+ spanner.getInstanceAdminClient().deleteInstance(instanceId);
+ }
+ });
+ assertThat(out)
+ .contains(
+ String.format(
+ "Instance %s was successfully created",
+ InstanceId.of(dbId.getInstanceId().getProject(), instanceId)));
+ }
+
+ private static int countOccurrences(String input, String search) {
+ return input.split(search).length - 1;
+ }
+
+ private static String toComparableId(String baseId, String existingId) {
+ String zeroUuid = "00000000-0000-0000-0000-0000-00000000";
+ int shouldBeLength = (baseId + "-" + zeroUuid).length();
+ int missingLength = shouldBeLength - existingId.length();
+ return existingId + zeroUuid.substring(zeroUuid.length() - missingLength);
+ }
+
+ private static Pattern getTestDbIdPattern(String baseDbId) {
+ return Pattern.compile(
+ baseDbId + "-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{8}",
+ Pattern.CASE_INSENSITIVE);
+ }
+
+ static String formatForTest(String name) {
+ return name + "-" + UUID.randomUUID().toString().substring(0, DBID_LENGTH);
+ }
+
+ static class ShouldRetryBackupOperation implements Predicate {
+
+ private static final int MAX_ATTEMPTS = 20;
+ private int attempts = 0;
+
+ @Override
+ public boolean test(SpannerException e) {
+ if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION
+ && e.getMessage().contains("Please retry the operation once the pending")) {
+ attempts++;
+ if (attempts == MAX_ATTEMPTS) {
+ // Throw custom exception so it is easier to locate in the log why it went wrong.
+ throw SpannerExceptionFactory.newSpannerException(ErrorCode.DEADLINE_EXCEEDED,
+ String.format("Operation failed %d times because of other pending operations. "
+ + "Giving up operation.\n", attempts),
+ e);
+ }
+ // Wait one minute before retrying.
+ Uninterruptibles.sleepUninterruptibly(60L, TimeUnit.SECONDS);
+ return true;
+ }
+ return false;
+ }
+ }
+}