diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg index 5e86d37f07..56db68092d 100644 --- a/.kokoro/presubmit/graalvm-native-17.cfg +++ b/.kokoro/presubmit/graalvm-native-17.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.25.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.27.0" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg index 8e8cded782..6283184778 100644 --- a/.kokoro/presubmit/graalvm-native.cfg +++ b/.kokoro/presubmit/graalvm-native.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.25.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.27.0" } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index c7eae29cd8..c2b269ee2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [6.61.0](https://github.com/googleapis/java-spanner/compare/v6.60.1...v6.61.0) (2024-03-04) + + +### Features + +* Support float32 type ([#2894](https://github.com/googleapis/java-spanner/issues/2894)) ([19b7976](https://github.com/googleapis/java-spanner/commit/19b79764294e938ad85d02b7c0662db6ec3afeda)) + + +### Bug Fixes + +* Flaky test issue due to AbortedException. ([#2925](https://github.com/googleapis/java-spanner/issues/2925)) ([cd34c1d](https://github.com/googleapis/java-spanner/commit/cd34c1d3ae9a5a36f4d5516dcf7c3667a9cf015a)) + + +### Dependencies + +* Update dependency com.google.cloud:sdk-platform-java-config to v3.27.0 ([#2935](https://github.com/googleapis/java-spanner/issues/2935)) ([f8f835a](https://github.com/googleapis/java-spanner/commit/f8f835a9da705605c492e232a58276c39d1d7e6c)) +* Update dependency org.json:json to v20240303 ([#2936](https://github.com/googleapis/java-spanner/issues/2936)) ([1d7044e](https://github.com/googleapis/java-spanner/commit/1d7044e97d16f5296b7de020cd24b11cbe2a7df0)) + + +### Documentation + +* Samples and tests for backup Admin APIs and overall spanner Admin APIs. ([#2882](https://github.com/googleapis/java-spanner/issues/2882)) ([de13636](https://github.com/googleapis/java-spanner/commit/de1363645e03f46deed5be41f90ddfed72766751)) +* Update all public documents to use auto-generated admin clients. ([#2928](https://github.com/googleapis/java-spanner/issues/2928)) ([ccb110a](https://github.com/googleapis/java-spanner/commit/ccb110ad6835557870933c95cfd76580fd317a16)) + ## [6.60.1](https://github.com/googleapis/java-spanner/compare/v6.60.0...v6.60.1) (2024-02-23) diff --git a/README.md b/README.md index 257ec20bc1..2172a1582f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.60.0 + 6.60.1 ``` @@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-spanner:6.60.0' +implementation 'com.google.cloud:google-cloud-spanner:6.60.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.60.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.60.1" ``` @@ -509,37 +509,44 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Update Jsonb Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.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/UpdateJsonbDataSample.java) | | Update Numeric Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateNumericDataSample.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/UpdateNumericDataSample.java) | | Update Using Dml Returning Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateUsingDmlReturningSample.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/UpdateUsingDmlReturningSample.java) | -| Add And Drop Database Role | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddAndDropDatabaseRole.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/AddAndDropDatabaseRole.java) | -| Add Json Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonColumnSample.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/AddJsonColumnSample.java) | -| Add Jsonb Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonbColumnSample.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/AddJsonbColumnSample.java) | -| 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) | -| 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) | -| Create Instance With Autoscaling Config Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigExample.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/CreateInstanceWithAutoscalingConfigExample.java) | -| Create Instance With Processing Units Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsExample.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/CreateInstanceWithProcessingUnitsExample.java) | -| Create Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateSequenceSample.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/CreateSequenceSample.java) | -| Create 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/CreateTableWithForeignKeyDeleteCascadeSample.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/CreateTableWithForeignKeyDeleteCascadeSample.java) | -| Delete Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/DeleteInstanceConfigSample.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/DeleteInstanceConfigSample.java) | -| Drop Foreign Key Constraint Delete Cascade Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSample.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/DropForeignKeyConstraintDeleteCascadeSample.java) | -| Drop Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropSequenceSample.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/DropSequenceSample.java) | -| Enable Fine Grained Access | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/EnableFineGrainedAccess.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/EnableFineGrainedAccess.java) | -| Get Database Ddl Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetDatabaseDdlSample.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/GetDatabaseDdlSample.java) | -| Get Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetInstanceConfigSample.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/GetInstanceConfigSample.java) | -| List Database Roles | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabaseRoles.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/ListDatabaseRoles.java) | -| List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabasesSample.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/ListDatabasesSample.java) | -| List Instance Config Operations Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigOperationsSample.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/ListInstanceConfigOperationsSample.java) | -| List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigsSample.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/ListInstanceConfigsSample.java) | -| Pg Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgAlterSequenceSample.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/PgAlterSequenceSample.java) | -| Pg Case Sensitivity Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCaseSensitivitySample.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/PgCaseSensitivitySample.java) | -| 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) | -| 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) | +| Add And Drop Database Role | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddAndDropDatabaseRole.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/archived/AddAndDropDatabaseRole.java) | +| Add Json Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonColumnSample.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/archived/AddJsonColumnSample.java) | +| Add Jsonb Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonbColumnSample.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/archived/AddJsonbColumnSample.java) | +| Add Numeric Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/AddNumericColumnSample.java) | +| Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/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/archived/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/archived/AlterTableWithForeignKeyDeleteCascadeSample.java) | +| Copy Backup Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/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/archived/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/archived/CreateBackupWithEncryptionKey.java) | +| Create Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSample.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/archived/CreateDatabaseWithDefaultLeaderSample.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/archived/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/archived/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/archived/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/archived/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/archived/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/archived/CreateInstanceConfigSample.java) | +| Create Instance Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/CreateInstanceExample.java) | +| Create Instance With Autoscaling Config Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigExample.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/archived/CreateInstanceWithAutoscalingConfigExample.java) | +| Create Instance With Processing Units Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithProcessingUnitsExample.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/archived/CreateInstanceWithProcessingUnitsExample.java) | +| Create Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateSequenceSample.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/archived/CreateSequenceSample.java) | +| Create 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/archived/CreateTableWithForeignKeyDeleteCascadeSample.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/archived/CreateTableWithForeignKeyDeleteCascadeSample.java) | +| Delete Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/DeleteInstanceConfigSample.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/archived/DeleteInstanceConfigSample.java) | +| Drop Foreign Key Constraint Delete Cascade Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSample.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/archived/DropForeignKeyConstraintDeleteCascadeSample.java) | +| Drop Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropSequenceSample.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/archived/DropSequenceSample.java) | +| Enable Fine Grained Access | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/EnableFineGrainedAccess.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/archived/EnableFineGrainedAccess.java) | +| Get Database Ddl Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetDatabaseDdlSample.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/archived/GetDatabaseDdlSample.java) | +| Get Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetInstanceConfigSample.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/archived/GetInstanceConfigSample.java) | +| List Database Roles | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabaseRoles.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/archived/ListDatabaseRoles.java) | +| List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabasesSample.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/archived/ListDatabasesSample.java) | +| List Instance Config Operations Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigOperationsSample.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/archived/ListInstanceConfigOperationsSample.java) | +| List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigsSample.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/archived/ListInstanceConfigsSample.java) | +| Pg Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgAlterSequenceSample.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/archived/PgAlterSequenceSample.java) | +| Pg Case Sensitivity Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCaseSensitivitySample.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/archived/PgCaseSensitivitySample.java) | +| Pg Create Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/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/archived/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/archived/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/archived/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/archived/PgInterleavedTableSample.java) | +| Pg Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/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/archived/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/archived/RestoreBackupWithEncryptionKey.java) | +| Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/SpannerSample.java) | +| Update Database Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/archived/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/archived/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/archived/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/archived/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/archived/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/archived/UpdateInstanceConfigSample.java) | @@ -643,7 +650,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.60.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.60.1 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index 0ef292d4ca..8df354deb8 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.60.1 + 6.61.0 pom com.google.cloud sdk-platform-java-config - 3.25.0 + 3.27.0 Google Cloud Spanner BOM @@ -53,43 +53,43 @@ com.google.cloud google-cloud-spanner - 6.60.1 + 6.61.0 com.google.cloud google-cloud-spanner test-jar - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 2d563185aa..495d9edfb9 100644 --- a/google-cloud-spanner-executor/pom.xml +++ b/google-cloud-spanner-executor/pom.xml @@ -5,14 +5,14 @@ 4.0.0 com.google.cloud google-cloud-spanner-executor - 6.60.1 + 6.61.0 jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index fbbc0153f8..eaf7637b0b 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -506,6 +506,48 @@ com.google.cloud.spanner.connection.StatementResult execute(com.google.cloud.spanner.Statement, java.util.Set) + + + 7012 + com/google/cloud/spanner/StructReader + float getFloat(int) + + + 7012 + com/google/cloud/spanner/StructReader + float getFloat(java.lang.String) + + + 7012 + com/google/cloud/spanner/StructReader + float[] getFloatArray(int) + + + 7012 + com/google/cloud/spanner/StructReader + float[] getFloatArray(java.lang.String) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getFloatList(int) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getFloatList(java.lang.String) + + + 7013 + com/google/cloud/spanner/Value + float getFloat32() + + + 7013 + com/google/cloud/spanner/Value + java.util.List getFloat32Array() + + 7012 @@ -569,7 +611,7 @@ void setSpan(io.opencensus.trace.Span) void setSpan(com.google.cloud.spanner.ISpan) - + 7012 @@ -580,5 +622,5 @@ 7012 com/google/cloud/spanner/connection/Connection void setDirectedRead(com.google.spanner.v1.DirectedReadOptions) - + diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index ef121f7a32..b2e0351c0a 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner - 6.60.1 + 6.61.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 google-cloud-spanner @@ -388,7 +388,7 @@ org.json json - 20240205 + 20240303 test diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java index 6cce03e72c..2cf93fb92e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java @@ -173,16 +173,44 @@ static double valueProtoToFloat64(com.google.protobuf.Value proto) { return proto.getNumberValue(); } + static float valueProtoToFloat32(com.google.protobuf.Value proto) { + if (proto.getKindCase() == KindCase.STRING_VALUE) { + switch (proto.getStringValue()) { + case "-Infinity": + return Float.NEGATIVE_INFINITY; + case "Infinity": + return Float.POSITIVE_INFINITY; + case "NaN": + return Float.NaN; + default: + // Fall-through to handling below to produce an error. + } + } + if (proto.getKindCase() != KindCase.NUMBER_VALUE) { + throw newSpannerException( + ErrorCode.INTERNAL, + "Invalid value for column type " + + Type.float32() + + " expected NUMBER_VALUE or STRING_VALUE with value one of" + + " \"Infinity\", \"-Infinity\", or \"NaN\" but was " + + proto.getKindCase() + + (proto.getKindCase() == KindCase.STRING_VALUE + ? " with value \"" + proto.getStringValue() + "\"" + : "")); + } + return (float) proto.getNumberValue(); + } + static NullPointerException throwNotNull(int columnIndex) { throw new NullPointerException( "Cannot call array getter for column " + columnIndex + " with null elements"); } /** - * Memory-optimized base class for {@code ARRAY} and {@code ARRAY} types. Both of - * these involve conversions from the type yielded by JSON parsing, which are {@code String} and - * {@code BigDecimal} respectively. Rather than construct new wrapper objects for each array - * element, we use primitive arrays and a {@code BitSet} to track nulls. + * Memory-optimized base class for {@code ARRAY}, {@code ARRAY} and {@code + * ARRAY} types. All of these involve conversions from the type yielded by JSON parsing, + * which are {@code String} and {@code BigDecimal} respectively. Rather than construct new wrapper + * objects for each array element, we use primitive arrays and a {@code BitSet} to track nulls. */ abstract static class PrimitiveArray extends AbstractList { private final A data; @@ -264,6 +292,31 @@ Long get(long[] array, int i) { } } + static class Float32Array extends PrimitiveArray { + Float32Array(ListValue protoList) { + super(protoList); + } + + Float32Array(float[] data, BitSet nulls) { + super(data, nulls, data.length); + } + + @Override + float[] newArray(int size) { + return new float[size]; + } + + @Override + void setProto(float[] array, int i, com.google.protobuf.Value protoValue) { + array[i] = valueProtoToFloat32(protoValue); + } + + @Override + Float get(float[] array, int i) { + return array[i]; + } + } + static class Float64Array extends PrimitiveArray { Float64Array(ListValue protoList) { super(protoList); @@ -306,6 +359,11 @@ protected long getLongInternal(int columnIndex) { return currRow().getLongInternal(columnIndex); } + @Override + protected float getFloatInternal(int columnIndex) { + return currRow().getFloatInternal(columnIndex); + } + @Override protected double getDoubleInternal(int columnIndex) { return currRow().getDoubleInternal(columnIndex); @@ -382,6 +440,16 @@ protected List getLongListInternal(int columnIndex) { return currRow().getLongListInternal(columnIndex); } + @Override + protected float[] getFloatArrayInternal(int columnIndex) { + return currRow().getFloatArrayInternal(columnIndex); + } + + @Override + protected List getFloatListInternal(int columnIndex) { + return currRow().getFloatListInternal(columnIndex); + } + @Override protected double[] getDoubleArrayInternal(int columnIndex) { return currRow().getDoubleArrayInternal(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java index ef6f63d52e..a11c573233 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java @@ -43,6 +43,10 @@ public abstract class AbstractStructReader implements StructReader { protected abstract long getLongInternal(int columnIndex); + protected float getFloatInternal(int columnIndex) { + throw new UnsupportedOperationException("Not implemented"); + } + protected abstract double getDoubleInternal(int columnIndex); protected abstract BigDecimal getBigDecimalInternal(int columnIndex); @@ -94,6 +98,14 @@ protected Value getValueInternal(int columnIndex) { protected abstract List getLongListInternal(int columnIndex); + protected float[] getFloatArrayInternal(int columnIndex) { + throw new UnsupportedOperationException("Not implemented"); + } + + protected List getFloatListInternal(int columnIndex) { + throw new UnsupportedOperationException("Not implemented"); + } + protected abstract double[] getDoubleArrayInternal(int columnIndex); protected abstract List getDoubleListInternal(int columnIndex); @@ -164,6 +176,19 @@ public long getLong(String columnName) { return getLongInternal(columnIndex); } + @Override + public float getFloat(int columnIndex) { + checkNonNullOfType(columnIndex, Type.float32(), columnIndex); + return getFloatInternal(columnIndex); + } + + @Override + public float getFloat(String columnName) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfType(columnIndex, Type.float32(), columnName); + return getFloatInternal(columnIndex); + } + @Override public double getDouble(int columnIndex) { checkNonNullOfType(columnIndex, Type.float64(), columnIndex); @@ -368,6 +393,32 @@ public List getLongList(String columnName) { return getLongListInternal(columnIndex); } + @Override + public float[] getFloatArray(int columnIndex) { + checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnIndex); + return getFloatArrayInternal(columnIndex); + } + + @Override + public float[] getFloatArray(String columnName) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnName); + return getFloatArrayInternal(columnIndex); + } + + @Override + public List getFloatList(int columnIndex) { + checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnIndex); + return getFloatListInternal(columnIndex); + } + + @Override + public List getFloatList(String columnName) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnName); + return getFloatListInternal(columnIndex); + } + @Override public double[] getDoubleArray(int columnIndex) { checkNonNullOfType(columnIndex, Type.array(Type.float64()), columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java index 97c39c00a8..b3e37ffcdd 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java @@ -125,6 +125,18 @@ public long getLong(String columnName) { return delegate.get().getLong(columnName); } + @Override + public float getFloat(int columnIndex) { + checkValidState(); + return delegate.get().getFloat(columnIndex); + } + + @Override + public float getFloat(String columnName) { + checkValidState(); + return delegate.get().getFloat(columnName); + } + @Override public double getDouble(int columnIndex) { checkValidState(); @@ -267,6 +279,30 @@ public List getLongList(String columnName) { return delegate.get().getLongList(columnName); } + @Override + public float[] getFloatArray(int columnIndex) { + checkValidState(); + return delegate.get().getFloatArray(columnIndex); + } + + @Override + public float[] getFloatArray(String columnName) { + checkValidState(); + return delegate.get().getFloatArray(columnName); + } + + @Override + public List getFloatList(int columnIndex) { + checkValidState(); + return delegate.get().getFloatList(columnIndex); + } + + @Override + public List getFloatList(String columnName) { + checkValidState(); + return delegate.get().getFloatList(columnName); + } + @Override public double[] getDoubleArray(int columnIndex) { checkValidState(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java index 6be649ae7c..a6769acfad 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner; import static com.google.cloud.spanner.AbstractResultSet.throwNotNull; +import static com.google.cloud.spanner.AbstractResultSet.valueProtoToFloat32; import static com.google.cloud.spanner.AbstractResultSet.valueProtoToFloat64; import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException; import static com.google.common.base.Preconditions.checkArgument; @@ -24,6 +25,7 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.AbstractResultSet.Float32Array; import com.google.cloud.spanner.AbstractResultSet.Float64Array; import com.google.cloud.spanner.AbstractResultSet.Int64Array; import com.google.cloud.spanner.AbstractResultSet.LazyByteArray; @@ -83,6 +85,9 @@ private Object writeReplace() { case FLOAT64: builder.set(fieldName).to((Double) value); break; + case FLOAT32: + builder.set(fieldName).to((Float) value); + break; case NUMERIC: builder.set(fieldName).to((BigDecimal) value); break; @@ -135,6 +140,9 @@ private Object writeReplace() { case FLOAT64: builder.set(fieldName).toFloat64Array((Iterable) value); break; + case FLOAT32: + builder.set(fieldName).toFloat32Array((Iterable) value); + break; case NUMERIC: builder.set(fieldName).toNumericArray((Iterable) value); break; @@ -259,6 +267,8 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot return Long.parseLong(proto.getStringValue()); case FLOAT64: return valueProtoToFloat64(proto); + case FLOAT32: + return valueProtoToFloat32(proto); case NUMERIC: checkType(fieldType, proto, KindCase.STRING_VALUE); return new BigDecimal(proto.getStringValue()); @@ -310,11 +320,13 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) { switch (elementType.getCode()) { case INT64: case ENUM: - // For int64/float64/enum types, use custom containers. These avoid wrapper object - // creation for non-null arrays. + // For int64/float64/float32/enum types, use custom containers. + // These avoid wrapper object creation for non-null arrays. return new Int64Array(listValue); case FLOAT64: return new Float64Array(listValue); + case FLOAT32: + return new Float32Array(listValue); case BOOL: case NUMERIC: case PG_NUMERIC: @@ -418,6 +430,12 @@ protected double getDoubleInternal(int columnIndex) { return (Double) rowData.get(columnIndex); } + @Override + protected float getFloatInternal(int columnIndex) { + ensureDecoded(columnIndex); + return (Float) rowData.get(columnIndex); + } + @Override protected BigDecimal getBigDecimalInternal(int columnIndex) { ensureDecoded(columnIndex); @@ -537,6 +555,8 @@ protected Value getValueInternal(int columnIndex) { return Value.pgNumeric(isNull ? null : getStringInternal(columnIndex)); case FLOAT64: return Value.float64(isNull ? null : getDoubleInternal(columnIndex)); + case FLOAT32: + return Value.float32(isNull ? null : getFloatInternal(columnIndex)); case STRING: return Value.string(isNull ? null : getStringInternal(columnIndex)); case JSON: @@ -570,6 +590,8 @@ protected Value getValueInternal(int columnIndex) { return Value.pgNumericArray(isNull ? null : getStringListInternal(columnIndex)); case FLOAT64: return Value.float64Array(isNull ? null : getDoubleListInternal(columnIndex)); + case FLOAT32: + return Value.float32Array(isNull ? null : getFloatListInternal(columnIndex)); case STRING: return Value.stringArray(isNull ? null : getStringListInternal(columnIndex)); case JSON: @@ -652,6 +674,18 @@ protected Float64Array getDoubleListInternal(int columnIndex) { return (Float64Array) rowData.get(columnIndex); } + @Override + protected float[] getFloatArrayInternal(int columnIndex) { + ensureDecoded(columnIndex); + return getFloatListInternal(columnIndex).toPrimitiveArray(columnIndex); + } + + @Override + protected Float32Array getFloatListInternal(int columnIndex) { + ensureDecoded(columnIndex); + return (Float32Array) rowData.get(columnIndex); + } + @Override @SuppressWarnings("unchecked") // We know ARRAY produces a List. protected List getBigDecimalListInternal(int columnIndex) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java index 73995a20df..6c869c549f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java @@ -364,6 +364,8 @@ public int hashCode() { * mutation equality to check for modifications before committing. We noticed that when NaNs where * used the template would always indicate a modification was present, when it turned out not to * be the case. For more information see b/206339664. + * + *

Similar change is being done while calculating `Value.hashCode()`. */ private boolean areValuesEqual(List values, List otherValues) { if (values == null && otherValues == null) { @@ -385,9 +387,19 @@ private boolean areValuesEqual(List values, List otherValues) { } private boolean isNaN(Value value) { - return !value.isNull() - && value.getType().equals(Type.float64()) - && Double.isNaN(value.getFloat64()); + return !value.isNull() && (isFloat64NaN(value) || isFloat32NaN(value)); + } + + // Checks if the Float64 value is either a "Double" or a "Float" NaN. + // Refer the comment above `areValuesEqual` for more details. + private boolean isFloat64NaN(Value value) { + return value.getType().equals(Type.float64()) && Double.isNaN(value.getFloat64()); + } + + // Checks if the Float32 value is either a "Double" or a "Float" NaN. + // Refer the comment above `areValuesEqual` for more details. + private boolean isFloat32NaN(Value value) { + return value.getType().equals(Type.float32()) && Float.isNaN(value.getFloat32()); } static void toProto(Iterable mutations, List out) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java index a6cc7c729e..3d12cf5ad2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java @@ -236,6 +236,16 @@ public long getLong(String columnName) { return getCurrentRowAsStruct().getLong(columnName); } + @Override + public float getFloat(int columnIndex) { + return getCurrentRowAsStruct().getFloat(columnIndex); + } + + @Override + public float getFloat(String columnName) { + return getCurrentRowAsStruct().getFloat(columnName); + } + @Override public double getDouble(int columnIndex) { return getCurrentRowAsStruct().getDouble(columnIndex); @@ -388,6 +398,26 @@ public List getLongList(String columnName) { return getCurrentRowAsStruct().getLongList(columnName); } + @Override + public float[] getFloatArray(int columnIndex) { + return getCurrentRowAsStruct().getFloatArray(columnIndex); + } + + @Override + public float[] getFloatArray(String columnName) { + return getCurrentRowAsStruct().getFloatArray(columnName); + } + + @Override + public List getFloatList(int columnIndex) { + return getCurrentRowAsStruct().getFloatList(columnIndex); + } + + @Override + public List getFloatList(String columnName) { + return getCurrentRowAsStruct().getFloatList(columnName); + } + @Override public double[] getDoubleArray(int columnIndex) { return getCurrentRowAsStruct().getDoubleArray(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java index 40c30148d0..0e65fa7f1b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.primitives.Booleans; import com.google.common.primitives.Doubles; +import com.google.common.primitives.Floats; import com.google.common.primitives.Longs; import com.google.protobuf.AbstractMessage; import com.google.protobuf.ProtocolMessageEnum; @@ -180,6 +181,11 @@ protected long getLongInternal(int columnIndex) { return values.get(columnIndex).getInt64(); } + @Override + protected float getFloatInternal(int columnIndex) { + return values.get(columnIndex).getFloat32(); + } + @Override protected double getDoubleInternal(int columnIndex) { return values.get(columnIndex).getFloat64(); @@ -261,6 +267,16 @@ protected List getLongListInternal(int columnIndex) { return values.get(columnIndex).getInt64Array(); } + @Override + protected float[] getFloatArrayInternal(int columnIndex) { + return Floats.toArray(getFloatListInternal(columnIndex)); + } + + @Override + protected List getFloatListInternal(int columnIndex) { + return values.get(columnIndex).getFloat32Array(); + } + @Override protected double[] getDoubleArrayInternal(int columnIndex) { return Doubles.toArray(getDoubleListInternal(columnIndex)); @@ -382,6 +398,8 @@ private Object getAsObject(int columnIndex) { case INT64: case ENUM: return getLongInternal(columnIndex); + case FLOAT32: + return getFloatInternal(columnIndex); case FLOAT64: return getDoubleInternal(columnIndex); case NUMERIC: @@ -410,6 +428,8 @@ private Object getAsObject(int columnIndex) { case INT64: case ENUM: return getLongListInternal(columnIndex); + case FLOAT32: + return getFloatListInternal(columnIndex); case FLOAT64: return getDoubleListInternal(columnIndex); case NUMERIC: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java index fd8cb77f39..f9967db045 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java @@ -123,6 +123,22 @@ public interface StructReader { */ long getLong(String columnName); + /** + * @param columnIndex index of the column + * @return the value of a non-{@code NULL} column with type {@link Type#float32()}. + */ + default float getFloat(int columnIndex) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * @param columnName name of the column + * @return the value of a non-{@code NULL} column with type {@link Type#float32()}. + */ + default float getFloat(String columnName) { + throw new UnsupportedOperationException("method should be overwritten"); + } + /** * @param columnIndex index of the column * @return the value of a non-{@code NULL} column with type {@link Type#float64()}. @@ -361,6 +377,44 @@ default Value getValue(String columnName) { */ List getLongList(String columnName); + /** + * @param columnIndex index of the column + * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())}. + * @throws NullPointerException if any element of the array value is {@code NULL}. If the array + * may contain {@code NULL} values, use {@link #getFloatList(int)} instead. + */ + default float[] getFloatArray(int columnIndex) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * @param columnName name of the column + * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())}. + * @throws NullPointerException if any element of the array value is {@code NULL}. If the array + * may contain {@code NULL} values, use {@link #getFloatList(String)} instead. + */ + default float[] getFloatArray(String columnName) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * @param columnIndex index of the column + * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())} The + * list returned by this method is lazily constructed. Create a copy of it if you intend to + * access each element in the list multiple times. + */ + default List getFloatList(int columnIndex) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * @param columnName name of the column + * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())} The + * list returned by this method is lazily constructed. Create a copy of it if you intend to + * access each element in the list multiple times. + */ + List getFloatList(String columnName); + /** * @param columnIndex index of the column * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float64())}. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java index 348db5d04a..5d871227f5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java @@ -48,6 +48,7 @@ public final class Type implements Serializable { private static final Type TYPE_BOOL = new Type(Code.BOOL, null, null); private static final Type TYPE_INT64 = new Type(Code.INT64, null, null); + private static final Type TYPE_FLOAT32 = new Type(Code.FLOAT32, null, null); private static final Type TYPE_FLOAT64 = new Type(Code.FLOAT64, null, null); private static final Type TYPE_NUMERIC = new Type(Code.NUMERIC, null, null); private static final Type TYPE_PG_NUMERIC = new Type(Code.PG_NUMERIC, null, null); @@ -59,6 +60,7 @@ public final class Type implements Serializable { private static final Type TYPE_DATE = new Type(Code.DATE, null, null); private static final Type TYPE_ARRAY_BOOL = new Type(Code.ARRAY, TYPE_BOOL, null); private static final Type TYPE_ARRAY_INT64 = new Type(Code.ARRAY, TYPE_INT64, null); + private static final Type TYPE_ARRAY_FLOAT32 = new Type(Code.ARRAY, TYPE_FLOAT32, null); private static final Type TYPE_ARRAY_FLOAT64 = new Type(Code.ARRAY, TYPE_FLOAT64, null); private static final Type TYPE_ARRAY_NUMERIC = new Type(Code.ARRAY, TYPE_NUMERIC, null); private static final Type TYPE_ARRAY_PG_NUMERIC = new Type(Code.ARRAY, TYPE_PG_NUMERIC, null); @@ -89,9 +91,17 @@ public static Type int64() { return TYPE_INT64; } + /** + * Returns the descriptor for the {@code FLOAT32} type: a floating point type with the same value + * domain as a Java {@code float}. + */ + public static Type float32() { + return TYPE_FLOAT32; + } + /** * Returns the descriptor for the {@code FLOAT64} type: a floating point type with the same value - * domain as a Java {code double}. + * domain as a Java {@code double}. */ public static Type float64() { return TYPE_FLOAT64; @@ -174,6 +184,8 @@ public static Type array(Type elementType) { return TYPE_ARRAY_BOOL; case INT64: return TYPE_ARRAY_INT64; + case FLOAT32: + return TYPE_ARRAY_FLOAT32; case FLOAT64: return TYPE_ARRAY_FLOAT64; case NUMERIC: @@ -264,6 +276,7 @@ public enum Code { NUMERIC(TypeCode.NUMERIC, "unknown"), PG_NUMERIC(TypeCode.NUMERIC, "numeric", TypeAnnotationCode.PG_NUMERIC), FLOAT64(TypeCode.FLOAT64, "double precision"), + FLOAT32(TypeCode.FLOAT32, "real"), STRING(TypeCode.STRING, "character varying"), JSON(TypeCode.JSON, "unknown"), PG_JSONB(TypeCode.JSON, "jsonb", TypeAnnotationCode.PG_JSONB), @@ -565,6 +578,8 @@ static Type fromProto(com.google.spanner.v1.Type proto) { return bool(); case INT64: return int64(); + case FLOAT32: + return float32(); case FLOAT64: return float64(); case NUMERIC: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java index 3f0155e4a5..49a6c1e7bc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java @@ -149,6 +149,20 @@ public static Value int64(long v) { return new Int64Impl(false, v); } + /** + * Returns a {@code FLOAT32} value. + * + * @param v the value, which may be null + */ + public static Value float32(@Nullable Float v) { + return new Float32Impl(v == null, v == null ? 0 : v); + } + + /** Returns a {@code FLOAT32} value. */ + public static Value float32(float v) { + return new Float32Impl(false, v); + } + /** * Returns a {@code FLOAT64} value. * @@ -454,6 +468,40 @@ public static Value int64Array(@Nullable Iterable v) { return int64ArrayFactory.create(v); } + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values, which may be null to produce a value for which {@code + * isNull()} is {@code true} + */ + public static Value float32Array(@Nullable float[] v) { + return float32Array(v, 0, v == null ? 0 : v.length); + } + + /** + * Returns an {@code ARRAY} value that takes its elements from a region of an array. + * + * @param v the source of element values, which may be null to produce a value for which {@code + * isNull()} is {@code true} + * @param pos the start position of {@code v} to copy values from. Ignored if {@code v} is {@code + * null}. + * @param length the number of values to copy from {@code v}. Ignored if {@code v} is {@code + * null}. + */ + public static Value float32Array(@Nullable float[] v, int pos, int length) { + return float32ArrayFactory.create(v, pos, length); + } + + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + */ + public static Value float32Array(@Nullable Iterable v) { + return float32ArrayFactory.create(v); + } + /** * Returns an {@code ARRAY} value. * @@ -729,6 +777,13 @@ private Value() {} */ public abstract long getInt64(); + /** + * Returns the value of a {@code FLOAT32}-typed instance. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public abstract float getFloat32(); + /** * Returns the value of a {@code FLOAT64}-typed instance. * @@ -835,6 +890,14 @@ public T getProtoEnum( */ public abstract List getInt64Array(); + /** + * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself + * will never be {@code null}, elements of that list may be null. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public abstract List getFloat32Array(); + /** * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself * will never be {@code null}, elements of that list may be null. @@ -1052,6 +1115,23 @@ Value newValue(boolean isNull, BitSet nulls, long[] values) { return new Int64ArrayImpl(isNull, nulls, values); } }; + private static final PrimitiveArrayValueFactory float32ArrayFactory = + new PrimitiveArrayValueFactory() { + @Override + float[] newArray(int size) { + return new float[size]; + } + + @Override + void set(float[] arr, int i, Float value) { + arr[i] = value; + } + + @Override + Value newValue(boolean isNull, BitSet nulls, float[] values) { + return new Float32ArrayImpl(isNull, nulls, values); + } + }; private static final PrimitiveArrayValueFactory float64ArrayFactory = new PrimitiveArrayValueFactory() { @Override @@ -1122,6 +1202,11 @@ public long getInt64() { throw defaultGetter(Type.int64()); } + @Override + public float getFloat32() { + throw defaultGetter(Type.float32()); + } + @Override public double getFloat64() { throw defaultGetter(Type.float64()); @@ -1181,6 +1266,11 @@ public List getInt64Array() { throw defaultGetter(Type.array(Type.int64())); } + @Override + public List getFloat32Array() { + throw defaultGetter(Type.array(Type.float32())); + } + @Override public List getFloat64Array() { throw defaultGetter(Type.array(Type.float64())); @@ -1285,9 +1375,29 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - int result = Objects.hash(getType(), isNull); + Type typeToHash = getType(); + int valueHash = isNull ? 0 : valueHash(); + + /** + * We are relaxing equality values here, making sure that Double.NaNs and Float.NaNs are equal + * to each other. This is because our Cloud Spanner Import / Export template in Apache Beam + * uses the mutation equality to check for modifications before committing. We noticed that + * when NaNs where used the template would always indicate a modification was present, when it + * turned out not to be the case. + * + *

With FLOAT32 being introduced, we want to ensure the backward compatibility of the NaN + * equality checks that existed for FLOAT64. We're promoting the type to FLOAT64 while + * calculating the type hash when the value is a NaN. We're doing a similar type promotion + * while calculating valueHash of Float32 type. Note that this is not applicable for composite + * types containing FLOAT32. + */ + if (type.getCode() == Type.Code.FLOAT32 && !isNull && Float.isNaN(getFloat32())) { + typeToHash = Type.float64(); + } + + int result = Objects.hash(typeToHash, isNull); if (!isNull) { - result = 31 * result + valueHash(); + result = 31 * result + valueHash; } return result; } @@ -1492,6 +1602,50 @@ int valueHash() { } } + private static class Float32Impl extends AbstractValue { + private final float value; + + private Float32Impl(boolean isNull, float value) { + super(isNull, Type.float32()); + this.value = value; + } + + @Override + public float getFloat32() { + checkNotNull(); + return value; + } + + @Override + com.google.protobuf.Value valueToProto() { + return com.google.protobuf.Value.newBuilder().setNumberValue(value).build(); + } + + @Override + void valueToString(StringBuilder b) { + b.append(value); + } + + @Override + boolean valueEquals(Value v) { + // NaN == NaN always returns false, so we need a custom check. + if (Float.isNaN(this.value)) { + return Float.isNaN(((Float32Impl) v).value); + } + return ((Float32Impl) v).value == value; + } + + @Override + int valueHash() { + // For backward compatibility of NaN equality checks with Float64 NaNs. + // Refer the comment in `Value.hashCode()` for more details. + if (!isNull() && Float.isNaN(value)) { + return Double.valueOf(Double.NaN).hashCode(); + } + return Float.valueOf(value).hashCode(); + } + } + private static class Float64Impl extends AbstractValue { private final double value; @@ -2106,6 +2260,46 @@ int arrayHash() { } } + private static class Float32ArrayImpl extends PrimitiveArrayImpl { + private final float[] values; + + private Float32ArrayImpl(boolean isNull, BitSet nulls, float[] values) { + super(isNull, Type.float32(), nulls); + this.values = values; + } + + @Override + public List getFloat32Array() { + return getArray(); + } + + @Override + boolean valueEquals(Value v) { + Float32ArrayImpl that = (Float32ArrayImpl) v; + return Arrays.equals(values, that.values); + } + + @Override + int size() { + return values.length; + } + + @Override + Float getValue(int i) { + return values[i]; + } + + @Override + com.google.protobuf.Value getValueAsProto(int i) { + return com.google.protobuf.Value.newBuilder().setNumberValue(values[i]).build(); + } + + @Override + int arrayHash() { + return Arrays.hashCode(values); + } + } + private static class Float64ArrayImpl extends PrimitiveArrayImpl { private final double[] values; @@ -2588,6 +2782,8 @@ private Value getValue(int fieldIndex) { return Value.pgJsonb(value.getPgJsonb(fieldIndex)); case BYTES: return Value.bytes(value.getBytes(fieldIndex)); + case FLOAT32: + return Value.float32(value.getFloat(fieldIndex)); case FLOAT64: return Value.float64(value.getDouble(fieldIndex)); case NUMERIC: @@ -2622,6 +2818,8 @@ private Value getValue(int fieldIndex) { case BYTES: case PROTO: return Value.bytesArray(value.getBytesList(fieldIndex)); + case FLOAT32: + return Value.float32Array(value.getFloatList(fieldIndex)); case FLOAT64: return Value.float64Array(value.getDoubleList(fieldIndex)); case NUMERIC: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java index 9915e12175..d675686ffe 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java @@ -81,6 +81,16 @@ public R to(@Nullable Long value) { return handle(Value.int64(value)); } + /** Binds to {@code Value.float32(value)} */ + public R to(float value) { + return handle(Value.float32(value)); + } + + /** Binds to {@code Value.float32(value)} */ + public R to(@Nullable Float value) { + return handle(Value.float32(value)); + } + /** Binds to {@code Value.float64(value)} */ public R to(double value) { return handle(Value.float64(value)); @@ -198,6 +208,21 @@ public R toInt64Array(@Nullable Iterable values) { return handle(Value.int64Array(values)); } + /** Binds to {@code Value.float32Array(values)} */ + public R toFloat32Array(@Nullable float[] values) { + return handle(Value.float32Array(values)); + } + + /** Binds to {@code Value.float32Array(values, pos, length)} */ + public R toFloat32Array(@Nullable float[] values, int pos, int length) { + return handle(Value.float32Array(values, pos, length)); + } + + /** Binds to {@code Value.float32Array(values)} */ + public R toFloat32Array(@Nullable Iterable values) { + return handle(Value.float32Array(values)); + } + /** Binds to {@code Value.float64Array(values)} */ public R toFloat64Array(@Nullable double[] values) { return handle(Value.float64Array(values)); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java index e9aed66b6b..8ff367b248 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java @@ -678,10 +678,6 @@ public void setStatementTag(String tag) { */ private void checkSetRetryAbortsInternallyAvailable() { ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); - ConnectionPreconditions.checkState(isInTransaction(), "This connection has no transaction"); - ConnectionPreconditions.checkState( - getTransactionMode() == TransactionMode.READ_WRITE_TRANSACTION, - "RetryAbortsInternally is only available for read-write transactions"); ConnectionPreconditions.checkState( !isTransactionStarted(), "RetryAbortsInternally cannot be set after the transaction has started"); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index 268661aef9..8bca0b2834 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -39,6 +39,7 @@ import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; +import io.opentelemetry.api.OpenTelemetry; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -484,6 +485,7 @@ public static class Builder { private List statementExecutionInterceptors = Collections.emptyList(); private SpannerOptionsConfigurator configurator; + private OpenTelemetry openTelemetry; private Builder() {} @@ -633,6 +635,11 @@ Builder setCredentials(Credentials credentials) { return this; } + public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + /** @return the {@link ConnectionOptions} */ public ConnectionOptions build() { Preconditions.checkState(this.uri != null, "Connection URI is required"); @@ -691,6 +698,7 @@ public static Builder newBuilder() { private final boolean retryAbortsInternally; private final boolean useVirtualThreads; private final boolean useVirtualGrpcTransportThreads; + private final OpenTelemetry openTelemetry; private final List statementExecutionInterceptors; private final SpannerOptionsConfigurator configurator; @@ -792,6 +800,7 @@ private ConnectionOptions(Builder builder) { this.retryAbortsInternally = parseRetryAbortsInternally(this.uri); this.useVirtualThreads = parseUseVirtualThreads(this.uri); this.useVirtualGrpcTransportThreads = parseUseVirtualGrpcTransportThreads(this.uri); + this.openTelemetry = builder.openTelemetry; this.statementExecutionInterceptors = Collections.unmodifiableList(builder.statementExecutionInterceptors); this.configurator = builder.configurator; @@ -856,6 +865,14 @@ private static Integer parseIntegerProperty(String propertyName, String value) { return null; } + /** + * @return an instance of OpenTelemetry. If OpenTelemetry object is not set then null + * will be returned. + */ + OpenTelemetry getOpenTelemetry() { + return this.openTelemetry; + } + SpannerOptionsConfigurator getConfigurator() { return configurator; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java index 1b15ec5082..b5e4060ddd 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java @@ -180,6 +180,12 @@ public long getLong(String columnName) { return delegate.getLong(columnName); } + @Override + public float getFloat(int columnIndex) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloat(columnIndex); + } + @Override public double getDouble(int columnIndex) { Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); @@ -198,6 +204,12 @@ public BigDecimal getBigDecimal(int columnIndex) { return delegate.getBigDecimal(columnIndex); } + @Override + public float getFloat(String columnName) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloat(columnName); + } + @Override public double getDouble(String columnName) { Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); @@ -336,6 +348,30 @@ public List getLongList(String columnName) { return delegate.getLongList(columnName); } + @Override + public float[] getFloatArray(int columnIndex) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloatArray(columnIndex); + } + + @Override + public float[] getFloatArray(String columnName) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloatArray(columnName); + } + + @Override + public List getFloatList(int columnIndex) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloatList(columnIndex); + } + + @Override + public List getFloatList(String columnName) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getFloatList(columnName); + } + @Override public double[] getDoubleArray(int columnIndex) { Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java index a8de14e512..bd7c794a0f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java @@ -189,6 +189,18 @@ public long getLong(String columnName) { return delegate.getLong(columnName); } + @Override + public float getFloat(int columnIndex) { + checkClosed(); + return delegate.getFloat(columnIndex); + } + + @Override + public float getFloat(String columnName) { + checkClosed(); + return delegate.getFloat(columnName); + } + @Override public double getDouble(int columnIndex) { checkClosed(); @@ -345,6 +357,30 @@ public List getLongList(String columnName) { return delegate.getLongList(columnName); } + @Override + public float[] getFloatArray(int columnIndex) { + checkClosed(); + return delegate.getFloatArray(columnIndex); + } + + @Override + public float[] getFloatArray(String columnName) { + checkClosed(); + return delegate.getFloatArray(columnName); + } + + @Override + public List getFloatList(int columnIndex) { + checkClosed(); + return delegate.getFloatList(columnIndex); + } + + @Override + public List getFloatList(String columnName) { + checkClosed(); + return delegate.getFloatList(columnName); + } + @Override public double[] getDoubleArray(int columnIndex) { checkClosed(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index da8da78d92..b4ac7cf3a3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -29,6 +29,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Ticker; import io.grpc.ManagedChannelBuilder; +import io.opentelemetry.api.OpenTelemetry; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -156,6 +157,7 @@ static class SpannerPoolKey { private final String databaseRole; private final boolean routeToLeader; private final boolean useVirtualGrpcTransportThreads; + private final OpenTelemetry openTelemetry; @VisibleForTesting static SpannerPoolKey of(ConnectionOptions options) { @@ -183,6 +185,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { this.userAgent = options.getUserAgent(); this.routeToLeader = options.isRouteToLeader(); this.useVirtualGrpcTransportThreads = options.isUseVirtualGrpcTransportThreads(); + this.openTelemetry = options.getOpenTelemetry(); } @Override @@ -201,7 +204,8 @@ public boolean equals(Object o) { && Objects.equals(this.userAgent, other.userAgent) && Objects.equals(this.routeToLeader, other.routeToLeader) && Objects.equals( - this.useVirtualGrpcTransportThreads, other.useVirtualGrpcTransportThreads); + this.useVirtualGrpcTransportThreads, other.useVirtualGrpcTransportThreads) + && Objects.equals(this.openTelemetry, other.openTelemetry); } @Override @@ -216,7 +220,8 @@ public int hashCode() { this.databaseRole, this.userAgent, this.routeToLeader, - this.useVirtualGrpcTransportThreads); + this.useVirtualGrpcTransportThreads, + this.openTelemetry); } } @@ -349,6 +354,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { .setDatabaseRole(options.getDatabaseRole()) .setCredentials(options.getCredentials()); builder.setSessionPoolOption(key.sessionPoolOptions); + if (key.openTelemetry != null) { + builder.setOpenTelemetry(key.openTelemetry); + } if (key.numChannels != null) { builder.setNumChannels(key.numChannels); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractLatencyBenchmark.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractLatencyBenchmark.java new file mode 100644 index 0000000000..d7edf64030 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractLatencyBenchmark.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class AbstractLatencyBenchmark { + + /** Utility to print latency numbers. It computes metrics such as Average, P50, P95 and P99. */ + public void printResults(List results) { + if (results == null) { + return; + } + List orderedResults = new ArrayList<>(results); + Collections.sort(orderedResults); + System.out.println(); + System.out.printf("Total number of queries: %d\n", orderedResults.size()); + System.out.printf("Avg: %fs\n", avg(results)); + System.out.printf("P50: %fs\n", percentile(50, orderedResults)); + System.out.printf("P95: %fs\n", percentile(95, orderedResults)); + System.out.printf("P99: %fs\n", percentile(99, orderedResults)); + } + + private double percentile(int percentile, List orderedResults) { + int index = percentile * orderedResults.size() / 100; + Duration value = orderedResults.get(index); + Double convertedValue = convertDurationToFractionInSeconds(value); + return convertedValue; + } + + /** Returns the average duration in seconds from a list of duration values. */ + private double avg(List results) { + return results.stream() + .collect(Collectors.averagingDouble(this::convertDurationToFractionInSeconds)); + } + + private double convertDurationToFractionInSeconds(Duration duration) { + long seconds = duration.getSeconds(); + long nanos = duration.getNano(); + double fraction = (double) nanos / 1_000_000_000; + double value = seconds + fraction; + return value; + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java index 4fc3c67ceb..16dd51a36a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java @@ -58,6 +58,11 @@ protected long getLongInternal(int columnIndex) { return 0; } + @Override + protected float getFloatInternal(int columnIndex) { + return 0f; + } + @Override protected double getDoubleInternal(int columnIndex) { return 0; @@ -134,6 +139,16 @@ protected List getLongListInternal(int columnIndex) { return null; } + @Override + protected float[] getFloatArrayInternal(int columnIndex) { + return null; + } + + @Override + protected List getFloatListInternal(int columnIndex) { + return null; + } + @Override protected double[] getDoubleArrayInternal(int columnIndex) { return null; @@ -222,6 +237,13 @@ public static Collection parameters() { Collections.singletonList("getValue") }, {Type.int64(), "getLongInternal", 123L, "getLong", Collections.singletonList("getValue")}, + { + Type.float32(), + "getFloatInternal", + 2.0f, + "getFloat", + Collections.singletonList("getValue") + }, { Type.float64(), "getDoubleInternal", @@ -306,6 +328,20 @@ public static Collection parameters() { "getLongList", Arrays.asList("getLongArray", "getValue") }, + { + Type.array(Type.float32()), + "getFloatArrayInternal", + new float[] {1.0f, 2.0f}, + "getFloatArray", + Arrays.asList("getFloatList", "getValue") + }, + { + Type.array(Type.float32()), + "getFloatListInternal", + Arrays.asList(2.0f, 4.0f), + "getFloatList", + Arrays.asList("getFloatArray", "getValue") + }, { Type.array(Type.float64()), "getDoubleArrayInternal", diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplTest.java index 9a6bafa7bf..98497fbf14 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplTest.java @@ -193,9 +193,9 @@ public void withCallback() throws InterruptedException { }); } finishedLatch.await(); - // There should be between 1 and 4 callbacks, depending on the timing of the threads. + // There should be between 1 and 5 callbacks, depending on the timing of the threads. // Normally, there should be just 1 callback. - assertThat(callbackCounter.get()).isIn(Range.closed(1, 4)); + assertThat(callbackCounter.get()).isIn(Range.closed(1, 5)); assertThat(rowCounter.get()).isEqualTo(3); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BenchmarkingUtilityScripts.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BenchmarkingUtilityScripts.java new file mode 100644 index 0000000000..75e0ca3da0 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BenchmarkingUtilityScripts.java @@ -0,0 +1,142 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.gax.rpc.ServerStream; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.rpc.Code; +import com.google.spanner.v1.BatchWriteResponse; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Hosts a bunch of utility methods/scripts that can be used while performing benchmarks to load + * data, report latency metrics, etc. + * + *

Table schema used here: CREATE TABLE FOO ( id INT64 NOT NULL, BAZ INT64, BAR INT64, ) PRIMARY + * KEY(id); + */ +@Category(SlowTest.class) +@RunWith(JUnit4.class) +public class BenchmarkingUtilityScripts { + + // TODO(developer): Add your values here for PROJECT_ID, INSTANCE_ID, DATABASE_ID + // TODO(developer): By default these values are blank and should be replaced before a run. + private static final String PROJECT_ID = ""; + private static final String INSTANCE_ID = ""; + private static final String DATABASE_ID = ""; + private static final String SERVER_URL = "https://staging-wrenchworks.sandbox.googleapis.com"; + private static DatabaseClient client; + private static Spanner spanner; + + @BeforeClass + public static void beforeClass() { + final SpannerOptions.Builder optionsBuilder = + SpannerOptions.newBuilder() + .setProjectId(PROJECT_ID) + .setAutoThrottleAdministrativeRequests(); + if (!SERVER_URL.isEmpty()) { + optionsBuilder.setHost(SERVER_URL); + } + final SpannerOptions options = optionsBuilder.build(); + spanner = options.getService(); + client = spanner.getDatabaseClient(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DATABASE_ID)); + + // Delete all existing data from the table + client.write(ImmutableList.of(Mutation.delete("FOO", KeySet.all()))); + } + + @AfterClass + public static void afterClass() { + spanner.close(); + } + + /** + * A utility which bulk inserts 10^6 records into the database in batches. The method assumes that + * the instance/database/table is already created. It does not perform any admin operations. + * + *

Table schema used here: CREATE TABLE FOO ( id INT64 NOT NULL, BAZ INT64, BAR INT64, ) + * PRIMARY KEY(id); + */ + @Test + public void bulkInsertTestData() { + int key = 0; + List mutationGroups = new ArrayList<>(); + for (int batch = 0; batch < 100; batch++) { + List mutations = new LinkedList<>(); + for (int i = 0; i < 10000; i++) { + mutations.add( + Mutation.newInsertBuilder("FOO") + .set("id") + .to(key) + .set("BAZ") + .to(1) + .set("BAR") + .to(2) + .build()); + key++; + } + mutationGroups.add(MutationGroup.of(mutations)); + } + ServerStream responses = client.batchWriteAtLeastOnce(mutationGroups); + for (BatchWriteResponse response : responses) { + if (response.getStatus().getCode() == Code.OK_VALUE) { + System.out.printf( + "Mutation group indexes %s have been applied with commit timestamp %s", + response.getIndexesList(), response.getCommitTimestamp()); + } else { + System.out.printf( + "Mutation group indexes %s could not be applied with error code %s and " + + "error message %s", + response.getIndexesList(), + Code.forNumber(response.getStatus().getCode()), + response.getStatus().getMessage()); + } + } + } + + /** Collects all results from a collection of future objects. */ + public static List collectResults( + final ListeningScheduledExecutorService service, + final List>> results, + final int numOperations, + final Duration timeoutDuration) + throws Exception { + service.shutdown(); + if (!service.awaitTermination(timeoutDuration.toMinutes(), TimeUnit.MINUTES)) { + throw new TimeoutException(); + } + List allResults = new ArrayList<>(numOperations); + for (Future> result : results) { + allResults.addAll(result.get()); + } + return allResults; + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 8dfbb986eb..7cba80edd8 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -4083,6 +4083,7 @@ public void testGetAllTypesAsString() { int col = 0; assertAsString("true", resultSet, col++); assertAsString("100", resultSet, col++); + assertAsString("-3.14", resultSet, col++); assertAsString("3.14", resultSet, col++); assertAsString("6.626", resultSet, col++); assertAsString("test-string", resultSet, col++); @@ -4100,6 +4101,15 @@ public void testGetAllTypesAsString() { String.format("%d", Long.MAX_VALUE), String.format("%d", Long.MIN_VALUE), "NULL"), resultSet, col++); + assertAsString( + ImmutableList.of( + "NULL", + Float.valueOf(Float.MAX_VALUE).toString(), + Float.valueOf(Float.MIN_VALUE).toString(), + "NaN", + "3.14"), + resultSet, + col++); assertAsString(ImmutableList.of("NULL", "-12345.6789", "3.14"), resultSet, col++); assertAsString(ImmutableList.of("6.626", "NULL", "-8.9123"), resultSet, col++); assertAsString(ImmutableList.of("test-string1", "NULL", "test-string2"), resultSet, col++); @@ -4561,6 +4571,7 @@ private ListValue getRows(Dialect dialect) { ListValue.newBuilder() .addValues(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()) .addValues(com.google.protobuf.Value.newBuilder().setStringValue("100").build()) + .addValues(com.google.protobuf.Value.newBuilder().setNumberValue(-3.14f).build()) .addValues(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()) .addValues(com.google.protobuf.Value.newBuilder().setStringValue("6.626").build()) .addValues(com.google.protobuf.Value.newBuilder().setStringValue("test-string").build()) @@ -4609,6 +4620,31 @@ private ListValue getRows(Dialect dialect) { .setNullValue(NullValue.NULL_VALUE) .build()) .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(Float.MAX_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(Float.MIN_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("NaN") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(3.14f) + .build()) + .build())) .addValues( com.google.protobuf.Value.newBuilder() .setListValue( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DefaultBenchmark.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DefaultBenchmark.java new file mode 100644 index 0000000000..84578dd323 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DefaultBenchmark.java @@ -0,0 +1,270 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static com.google.cloud.spanner.BenchmarkingUtilityScripts.collectResults; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Stopwatch; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmarks for measuring existing latencies of various APIs using the Java Client. The benchmarks + * are bound to the Maven profile `benchmark` and can be executed like this: mvn clean test + * -DskipTests -Pbenchmark -Dbenchmark.name=DefaultBenchmark + * Test Table Schema : + * + *

CREATE TABLE FOO ( id INT64 NOT NULL, BAZ INT64, BAR INT64, ) PRIMARY KEY(id); + * + *

Below are a few considerations here: 1. We use all default options for this test because that + * is what most customers would be using. 2. The test schema uses a numeric primary key. To ensure + * that the reads/updates are distributed across a large query space, we insert 10^5 records. + * Utility at {@link BenchmarkingUtilityScripts} can be used for loading data. 3. For queries, we + * make sure that the query is sampled randomly across a large query space. This ensure we don't + * cause hot-spots. 4. For avoid cold start issues, we execute 1 query/update and ignore its latency + * from the final reported metrics. + */ +@BenchmarkMode(Mode.AverageTime) +@Fork(value = 1, warmups = 0) +@Measurement(batchSize = 1, iterations = 1, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 1) +public class DefaultBenchmark extends AbstractLatencyBenchmark { + + private static final String SELECT_QUERY = "SELECT ID FROM FOO WHERE ID = @id"; + private static final String UPDATE_QUERY = "UPDATE FOO SET BAR=1 WHERE ID = @id"; + private static final String ID_COLUMN_NAME = "id"; + + /** + * Used to determine how many concurrent requests are allowed. For ex - To simulate a low QPS + * scenario, using 1 thread means there will be 1 request. Use a value > 1 to have concurrent + * requests. + */ + private static final int PARALLEL_THREADS = 1; + + /** + * Total number of reads per test run for 1 thread. Increasing the value here will increase the + * duration of the benchmark. For ex - With PARALLEL_THREADS = 2, TOTAL_READS_PER_RUN = 200, there + * will be 400 read requests (200 on each thread). + */ + private static final int TOTAL_READS_PER_RUN = 12000; + + /** + * Total number of writes per test run for 1 thread. Increasing the value here will increase the + * duration of the benchmark. For ex - With PARALLEL_THREADS = 2, TOTAL_WRITES_PER_RUN = 200, + * there will be 400 write requests (200 on each thread). + */ + private static final int TOTAL_WRITES_PER_RUN = 4000; + + /** + * Number of requests which are used to initialise/warmup the benchmark. The latency number of + * these runs are ignored from the final reported results. + */ + private static final int WARMUP_REQUEST_COUNT = 1; + + /** + * Numbers of records in the sample table used in the benchmark. This is used in this benchmark to + * randomly choose a primary key and ensure that the reads are randomly distributed. This is done + * to ensure we don't end up reading/writing the same table record (leading to hot-spotting). + */ + private static final int TOTAL_RECORDS = 1000000; + + @State(Scope.Thread) + @AuxCounters(org.openjdk.jmh.annotations.AuxCounters.Type.EVENTS) + public static class BenchmarkState { + + // TODO(developer): Add your values here for PROJECT_ID, INSTANCE_ID, DATABASE_ID + private static final String INSTANCE_ID = ""; + private static final String DATABASE_ID = ""; + private static final String SERVER_URL = "https://staging-wrenchworks.sandbox.googleapis.com"; + private Spanner spanner; + private DatabaseClientImpl client; + + @Setup(Level.Iteration) + public void setup() throws Exception { + SpannerOptions options = + SpannerOptions.newBuilder() + .setSessionPoolOption( + SessionPoolOptions.newBuilder() + .setWaitForMinSessions(org.threeten.bp.Duration.ofSeconds(20)) + .build()) + .setHost(SERVER_URL) + .build(); + spanner = options.getService(); + client = + (DatabaseClientImpl) + spanner.getDatabaseClient( + DatabaseId.of(options.getProjectId(), INSTANCE_ID, DATABASE_ID)); + } + + @TearDown(Level.Iteration) + public void teardown() throws Exception { + spanner.close(); + } + } + + /** Measures the time needed to execute a burst of queries. */ + @Benchmark + public void burstQueries(final BenchmarkState server) throws Exception { + final DatabaseClientImpl client = server.client; + SessionPool pool = client.pool; + assertThat(pool.totalSessions()) + .isEqualTo(server.spanner.getOptions().getSessionPoolOptions().getMinSessions()); + + ListeningScheduledExecutorService service = + MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(PARALLEL_THREADS)); + List>> results = new ArrayList<>(PARALLEL_THREADS); + for (int i = 0; i < PARALLEL_THREADS; i++) { + results.add(service.submit(() -> runBenchmarksForQueries(server, TOTAL_READS_PER_RUN))); + } + collectResultsAndPrint(service, results, TOTAL_READS_PER_RUN); + } + + /** Measures the time needed to execute a burst of read and write requests. */ + @Benchmark + public void burstQueriesAndWrites(final BenchmarkState server) throws Exception { + final DatabaseClientImpl client = server.client; + SessionPool pool = client.pool; + assertThat(pool.totalSessions()) + .isEqualTo(server.spanner.getOptions().getSessionPoolOptions().getMinSessions()); + + ListeningScheduledExecutorService service = + MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(PARALLEL_THREADS)); + List>> results = new ArrayList<>(PARALLEL_THREADS); + for (int i = 0; i < PARALLEL_THREADS; i++) { + results.add(service.submit(() -> runBenchmarksForQueries(server, TOTAL_READS_PER_RUN))); + } + for (int i = 0; i < PARALLEL_THREADS; i++) { + results.add(service.submit(() -> runBenchmarkForUpdates(server, TOTAL_WRITES_PER_RUN))); + } + + collectResultsAndPrint(service, results, TOTAL_READS_PER_RUN + TOTAL_WRITES_PER_RUN); + } + + /** Measures the time needed to execute a burst of read and write requests. */ + @Benchmark + public void burstUpdates(final BenchmarkState server) throws Exception { + final DatabaseClientImpl client = server.client; + SessionPool pool = client.pool; + assertThat(pool.totalSessions()) + .isEqualTo(server.spanner.getOptions().getSessionPoolOptions().getMinSessions()); + + ListeningScheduledExecutorService service = + MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(PARALLEL_THREADS)); + List>> results = new ArrayList<>(PARALLEL_THREADS); + for (int i = 0; i < PARALLEL_THREADS; i++) { + results.add(service.submit(() -> runBenchmarkForUpdates(server, TOTAL_WRITES_PER_RUN))); + } + + collectResultsAndPrint(service, results, TOTAL_WRITES_PER_RUN); + } + + private List runBenchmarksForQueries( + final BenchmarkState server, int numberOfOperations) { + List results = new ArrayList<>(numberOfOperations); + // Execute one query to make sure everything has been warmed up. + executeWarmup(server); + + for (int i = 0; i < numberOfOperations; i++) { + results.add(executeQuery(server)); + } + return results; + } + + private void executeWarmup(final BenchmarkState server) { + for (int i = 0; i < WARMUP_REQUEST_COUNT; i++) { + executeQuery(server); + } + } + + private java.time.Duration executeQuery(final BenchmarkState server) { + Stopwatch watch = Stopwatch.createStarted(); + + try (ResultSet rs = server.client.singleUse().executeQuery(getRandomisedReadStatement())) { + while (rs.next()) { + int count = rs.getColumnCount(); + } + } + return watch.elapsed(); + } + + private List runBenchmarkForUpdates( + final BenchmarkState server, int numberOfOperations) { + List results = new ArrayList<>(numberOfOperations); + // Execute one query to make sure everything has been warmed up. + executeWarmup(server); + + // Execute one update to make sure everything has been warmed up. + executeUpdate(server); + + for (int i = 0; i < numberOfOperations; i++) { + results.add(executeUpdate(server)); + } + return results; + } + + private Duration executeUpdate(final BenchmarkState server) { + Stopwatch watch = Stopwatch.createStarted(); + + TransactionRunner runner = server.client.readWriteTransaction(); + runner.run(transaction -> transaction.executeUpdate(getRandomisedUpdateStatement())); + + return watch.elapsed(); + } + + static Statement getRandomisedReadStatement() { + int randomKey = ThreadLocalRandom.current().nextInt(TOTAL_RECORDS); + return Statement.newBuilder(SELECT_QUERY).bind(ID_COLUMN_NAME).to(randomKey).build(); + } + + static Statement getRandomisedUpdateStatement() { + int randomKey = ThreadLocalRandom.current().nextInt(TOTAL_RECORDS); + return Statement.newBuilder(UPDATE_QUERY).bind(ID_COLUMN_NAME).to(randomKey).build(); + } + + void collectResultsAndPrint( + ListeningScheduledExecutorService service, + List>> results, + int numOperationsPerThread) + throws Exception { + final List collectResults = + collectResults( + service, results, numOperationsPerThread * PARALLEL_THREADS, Duration.ofMinutes(60)); + printResults(collectResults); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java index cb73618d99..5e6a4ffc2c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java @@ -537,6 +537,8 @@ public void serialization() { Value.int64(null), Value.float64(1.0), Value.float64(null), + Value.float32(1.0f), + Value.float32(null), Value.bytes(ByteArray.fromBase64("abcd")), Value.bytesFromBase64( Base64.getEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8))), @@ -554,6 +556,8 @@ public void serialization() { Value.int64Array((long[]) null), Value.float64Array(new double[] {1.1, 2.2, 3.3}), Value.float64Array((double[]) null), + Value.float32Array(new float[] {1.1f, 2.2f, 3.3f}), + Value.float32Array((float[]) null), Value.bytesArray(Arrays.asList(ByteArray.fromBase64("abcd"), null)), Value.bytesArrayFromBase64( Arrays.asList( @@ -655,6 +659,22 @@ public void getDouble() { assertThat(resultSet.getDouble(0)).isWithin(0.0).of(Double.MAX_VALUE); } + @Test + public void getFloat() { + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata(makeMetadata(Type.struct(Type.StructField.of("f", Type.float32())))) + .addValues(Value.float32(Float.MIN_VALUE).toProto()) + .addValues(Value.float32(Float.MAX_VALUE).toProto()) + .build()); + consumer.onCompleted(); + + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getFloat(0)).isWithin(0.0f).of(Float.MIN_VALUE); + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getFloat(0)).isWithin(0.0f).of(Float.MAX_VALUE); + } + @Test public void getBigDecimal() { consumer.onPartialResultSet( @@ -877,6 +897,25 @@ public void getDoubleArray() { .inOrder(); } + @Test + public void getFloatArray() { + float[] floatArray = {Float.MAX_VALUE, Float.MIN_VALUE, 111, 333, 444, 0, -1, -2234}; + + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata(Type.struct(Type.StructField.of("f", Type.array(Type.float32()))))) + .addValues(Value.float32Array(floatArray).toProto()) + .build()); + consumer.onCompleted(); + + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getFloatArray(0)) + .usingTolerance(0.0) + .containsExactly(floatArray) + .inOrder(); + } + @Test public void getBigDecimalList() { List bigDecimalsList = new ArrayList<>(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java index c7f5e39606..c67c008467 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java @@ -37,6 +37,7 @@ import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; import com.google.cloud.spanner.TransactionRunner.TransactionCallable; import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl; +import com.google.cloud.spanner.connection.RandomResultSetGenerator; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.AbstractMessage; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java index f27aa405aa..7073ebd28e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java @@ -1284,6 +1284,9 @@ private Statement buildStatement( case DATE: builder.bind(fieldName).toDateArray(null); break; + case FLOAT32: + builder.bind(fieldName).toFloat32Array((Iterable) null); + break; case FLOAT64: builder.bind(fieldName).toFloat64Array((Iterable) null); break; @@ -1327,6 +1330,9 @@ private Statement buildStatement( case DATE: builder.bind(fieldName).to((Date) null); break; + case FLOAT32: + builder.bind(fieldName).to((Float) null); + break; case FLOAT64: builder.bind(fieldName).to((Double) null); break; @@ -1392,6 +1398,14 @@ private Statement buildStatement( GrpcStruct.decodeArrayValue( com.google.cloud.spanner.Type.date(), value.getListValue())); break; + case FLOAT32: + builder + .bind(fieldName) + .toFloat32Array( + (Iterable) + GrpcStruct.decodeArrayValue( + com.google.cloud.spanner.Type.float32(), value.getListValue())); + break; case FLOAT64: builder .bind(fieldName) @@ -1475,6 +1489,9 @@ private Statement buildStatement( case DATE: builder.bind(fieldName).to(Date.parseDate(value.getStringValue())); break; + case FLOAT32: + builder.bind(fieldName).to((float) value.getNumberValue()); + break; case FLOAT64: builder.bind(fieldName).to(value.getNumberValue()); break; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java index f38b5e47b8..0cfb57d4a2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java @@ -211,12 +211,37 @@ public void equalsAndHashCode() { Mutation.delete("T1", KeySet.singleKey(Key.of("k"))), Mutation.delete("T1", Key.of("k"))); // Test NaNs + // Refer the comment in `Value.hashCode()` for more details on NaN equality. tester.addEqualityGroup( Mutation.newInsertBuilder("T1").set("C").to(Double.NaN).build(), Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Double.NaN)).build(), Mutation.newInsertBuilder("T1").set("C").to(Float.NaN).build(), - Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Float.NaN)).build()); + Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Float.NaN)).build(), + Mutation.newInsertBuilder("T1").set("C").to(Value.float32(Float.NaN)).build()); + // Test NaN arrays + tester.addEqualityGroup( + Mutation.newInsertBuilder("T1").set("C").toFloat32Array(new float[] {Float.NaN}).build(), + Mutation.newInsertBuilder("T1") + .set("C") + .toFloat32Array(new float[] {Float.NaN}, 0, 1) + .build(), + Mutation.newInsertBuilder("T1") + .set("C") + .toFloat32Array(Collections.singletonList(Float.NaN)) + .build(), + Mutation.newInsertBuilder("T1") + .set("C") + .to(Value.float32Array(new float[] {Float.NaN})) + .build(), + Mutation.newInsertBuilder("T1") + .set("C") + .to(Value.float32Array(new float[] {Float.NaN}, 0, 1)) + .build(), + Mutation.newInsertBuilder("T1") + .set("C") + .to(Value.float32Array(Collections.singletonList(Float.NaN))) + .build()); tester.addEqualityGroup( Mutation.newInsertBuilder("T1").set("C").toFloat64Array(new double[] {Double.NaN}).build(), Mutation.newInsertBuilder("T1").set("C").toFloat64Array(new double[] {Float.NaN}).build(), @@ -270,6 +295,11 @@ public void equalsAndHashCode() { .set("C") .toFloat64Array(Arrays.asList(null, (double) Float.NaN)) .build()); + tester.addEqualityGroup( + Mutation.newInsertBuilder("T1") + .set("C") + .toFloat32Array(Arrays.asList(null, Float.NaN)) + .build()); tester.testEquals(); } @@ -523,11 +553,17 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .to((Long) null) .set("intValue") .to(Value.int64(1L)) - .set("float") + .set("float32") + .to(42.1f) + .set("float32Null") + .to((Float) null) + .set("float32Value") + .to(Value.float32(10f)) + .set("float64") .to(42.1) - .set("floatNull") + .set("float64Null") .to((Double) null) - .set("floatValue") + .set("float64Value") .to(Value.float64(10D)) .set("string") .to("str") @@ -583,11 +619,17 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .toInt64Array((long[]) null) .set("intArrValue") .to(Value.int64Array(ImmutableList.of(1L, 2L))) - .set("floatArr") + .set("float32Arr") + .toFloat32Array(new float[] {1.1f, 2.2f, 3.3f}) + .set("float32ArrNull") + .toFloat32Array((float[]) null) + .set("float32ArrValue") + .to(Value.float32Array(ImmutableList.of(10.1F, 10.2F, 10.3F))) + .set("float64Arr") .toFloat64Array(new double[] {1.1, 2.2, 3.3}) - .set("floatArrNull") + .set("float64ArrNull") .toFloat64Array((double[]) null) - .set("floatArrValue") + .set("float64ArrValue") .to(Value.float64Array(ImmutableList.of(10.1D, 10.2D, 10.3D))) .set("stringArr") .toStringArray(ImmutableList.of("one", "two")) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RandomResultSetGenerator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RandomResultSetGenerator.java index b5bbb9dd49..058429d3ba 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RandomResultSetGenerator.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RandomResultSetGenerator.java @@ -31,12 +31,15 @@ import com.google.spanner.v1.TypeCode; import java.util.Random; +/** @deprecated Use {@link com.google.cloud.spanner.connection.RandomResultSetGenerator} instead. */ +@Deprecated public class RandomResultSetGenerator { private static final Type[] TYPES = new Type[] { Type.newBuilder().setCode(TypeCode.BOOL).build(), Type.newBuilder().setCode(TypeCode.INT64).build(), Type.newBuilder().setCode(TypeCode.FLOAT64).build(), + Type.newBuilder().setCode(TypeCode.FLOAT32).build(), Type.newBuilder().setCode(TypeCode.STRING).build(), Type.newBuilder().setCode(TypeCode.BYTES).build(), Type.newBuilder().setCode(TypeCode.DATE).build(), @@ -53,6 +56,10 @@ public class RandomResultSetGenerator { .setCode(TypeCode.ARRAY) .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT32)) + .build(), Type.newBuilder() .setCode(TypeCode.ARRAY) .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING)) @@ -138,6 +145,9 @@ private void setRandomValue(Value.Builder builder, Type type) { case FLOAT64: builder.setNumberValue(random.nextDouble()); break; + case FLOAT32: + builder.setNumberValue(random.nextFloat()); + break; case INT64: builder.setStringValue(String.valueOf(random.nextLong())); break; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java index bb8d130914..6801c82b66 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadAsyncTest.java @@ -298,9 +298,12 @@ public void readOnlyTransaction() throws Exception { values2 = rs.toListAsync(input -> input.getString("Value"), executor); } } + + ApiFuture>> allValuesAsList = + ApiFutures.allAsList(Arrays.asList(values1, values2)); ApiFuture> allValues = ApiFutures.transform( - ApiFutures.allAsList(Arrays.asList(values1, values2)), + allValuesAsList, input -> Iterables.mergeSorted( input, diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java index a72c9872fa..8d97d9d894 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java @@ -170,6 +170,9 @@ private void assertRow(Struct actualRow, JSONArray expectedRow) throws Exception case INT64: assertThat(actualRow.getLong(i)).isEqualTo(expectedRow.getLong(i)); break; + case FLOAT32: + assertThat(actualRow.getFloat(i)).isEqualTo(expectedRow.getFloat(i)); + break; case FLOAT64: assertThat(actualRow.getDouble(i)).isEqualTo(expectedRow.getDouble(i)); break; @@ -208,6 +211,9 @@ private List getRawList(Struct actualRow, int index, Type elementType) { case INT64: rawList = actualRow.getLongList(index); break; + case FLOAT32: + rawList = actualRow.getFloatList(index); + break; case FLOAT64: rawList = actualRow.getDoubleList(index); break; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java index 8e1f257594..454bd3c70a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java @@ -31,6 +31,7 @@ import com.google.cloud.spanner.SingerProto.Genre; import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.common.primitives.Doubles; +import com.google.common.primitives.Floats; import com.google.common.primitives.Longs; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.AbstractMessage; @@ -53,6 +54,7 @@ public class ResultSetsTest { @Test public void resultSetIteration() { double doubleVal = 1.2; + float floatVal = 6.626f; BigDecimal bigDecimalVal = BigDecimal.valueOf(123, 2); String stringVal = "stringVal"; String jsonVal = "{\"color\":\"red\",\"value\":\"#f00\"}"; @@ -71,6 +73,7 @@ public void resultSetIteration() { boolean[] boolArray = {true, false, true, true, false}; long[] longArray = {Long.MAX_VALUE, Long.MIN_VALUE, 0, 1, -1}; double[] doubleArray = {Double.MIN_VALUE, Double.MAX_VALUE, 0, 1, -1, 1.2341}; + float[] floatArray = {Float.MIN_VALUE, Float.MAX_VALUE, 0, 1, -1, 1.2341f}; BigDecimal[] bigDecimalArray = { BigDecimal.valueOf(1, Integer.MAX_VALUE), BigDecimal.valueOf(1, Integer.MIN_VALUE), @@ -102,6 +105,7 @@ public void resultSetIteration() { Type.StructField.of("f2", Type.int64()), Type.StructField.of("f3", Type.bool()), Type.StructField.of("doubleVal", Type.float64()), + Type.StructField.of("floatVal", Type.float32()), Type.StructField.of("bigDecimalVal", Type.numeric()), Type.StructField.of("stringVal", Type.string()), Type.StructField.of("jsonVal", Type.json()), @@ -116,6 +120,7 @@ public void resultSetIteration() { Type.StructField.of("boolArray", Type.array(Type.bool())), Type.StructField.of("longArray", Type.array(Type.int64())), Type.StructField.of("doubleArray", Type.array(Type.float64())), + Type.StructField.of("floatArray", Type.array(Type.float32())), Type.StructField.of("bigDecimalArray", Type.array(Type.numeric())), Type.StructField.of("byteArray", Type.array(Type.bytes())), Type.StructField.of("timestampArray", Type.array(Type.timestamp())), @@ -138,6 +143,8 @@ public void resultSetIteration() { .to(Value.bool(true)) .set("doubleVal") .to(Value.float64(doubleVal)) + .set("floatVal") + .to(Value.float32(floatVal)) .set("bigDecimalVal") .to(Value.numeric(bigDecimalVal)) .set("stringVal") @@ -162,6 +169,8 @@ public void resultSetIteration() { .to(Value.int64Array(longArray)) .set("doubleArray") .to(Value.float64Array(doubleArray)) + .set("floatArray") + .to(Value.float32Array(floatArray)) .set("bigDecimalArray") .to(Value.numericArray(Arrays.asList(bigDecimalArray))) .set("byteArray") @@ -195,6 +204,8 @@ public void resultSetIteration() { .to(Value.bool(null)) .set("doubleVal") .to(Value.float64(doubleVal)) + .set("floatVal") + .to(Value.float32(floatVal)) .set("bigDecimalVal") .to(Value.numeric(bigDecimalVal)) .set("stringVal") @@ -219,6 +230,8 @@ public void resultSetIteration() { .to(Value.int64Array(longArray)) .set("doubleArray") .to(Value.float64Array(doubleArray)) + .set("floatArray") + .to(Value.float32Array(floatArray)) .set("bigDecimalArray") .to(Value.numericArray(Arrays.asList(bigDecimalArray))) .set("byteArray") @@ -274,6 +287,10 @@ public void resultSetIteration() { assertThat(rs.getValue("doubleVal").getFloat64()).isWithin(0.0).of(doubleVal); assertThat(rs.getDouble(columnIndex)).isWithin(0.0).of(doubleVal); assertThat(rs.getValue(columnIndex++).getFloat64()).isWithin(0.0).of(doubleVal); + assertThat(rs.getFloat(columnIndex)).isWithin(0.0f).of(floatVal); + assertThat(rs.getValue(columnIndex++).getFloat32()).isWithin(0.0f).of(floatVal); + assertThat(rs.getFloat("floatVal")).isWithin(0.0f).of(floatVal); + assertThat(rs.getValue("floatVal").getFloat32()).isWithin(0.0f).of(floatVal); assertThat(rs.getBigDecimal("bigDecimalVal")).isEqualTo(new BigDecimal("1.23")); assertThat(rs.getValue("bigDecimalVal")).isEqualTo(Value.numeric(new BigDecimal("1.23"))); assertThat(rs.getBigDecimal(columnIndex)).isEqualTo(new BigDecimal("1.23")); @@ -338,6 +355,17 @@ public void resultSetIteration() { assertThat(rs.getValue("doubleArray")).isEqualTo(Value.float64Array(doubleArray)); assertThat(rs.getDoubleList(columnIndex++)).isEqualTo(Doubles.asList(doubleArray)); assertThat(rs.getDoubleList("doubleArray")).isEqualTo(Doubles.asList(doubleArray)); + + assertThat(rs.getFloatArray(columnIndex)).usingTolerance(0.0f).containsAtLeast(floatArray); + assertThat(rs.getValue(columnIndex)).isEqualTo(Value.float32Array(floatArray)); + assertThat(rs.getFloatArray("floatArray")) + .usingTolerance(0.0f) + .containsExactly(floatArray) + .inOrder(); + assertThat(rs.getValue("floatArray")).isEqualTo(Value.float32Array(floatArray)); + assertThat(rs.getFloatList(columnIndex++)).isEqualTo(Floats.asList(floatArray)); + assertThat(rs.getFloatList("floatArray")).isEqualTo(Floats.asList(floatArray)); + assertThat(rs.getBigDecimalList(columnIndex)).isEqualTo(Arrays.asList(bigDecimalArray)); assertThat(rs.getValue(columnIndex++)) .isEqualTo(Value.numericArray(Arrays.asList(bigDecimalArray))); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java index 11b708ed48..6eedc058c5 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java @@ -110,6 +110,16 @@ Type newType() { }.test(); } + @Test + public void float32() { + new ScalarTypeTester(Type.Code.FLOAT32, TypeCode.FLOAT32) { + @Override + Type newType() { + return Type.float32(); + } + }.test(); + } + @Test public void float64() { new ScalarTypeTester(Type.Code.FLOAT64, TypeCode.FLOAT64) { @@ -307,6 +317,16 @@ Type newElementType() { }.test(); } + @Test + public void float32Array() { + new ArrayTypeTester(Type.Code.FLOAT32, TypeCode.FLOAT32, true) { + @Override + Type newElementType() { + return Type.float32(); + } + }.test(); + } + @Test public void float64Array() { new ArrayTypeTester(Type.Code.FLOAT64, TypeCode.FLOAT64, true) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java index d50814e84d..204880bf7d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java @@ -265,6 +265,14 @@ public static Long defaultLongWrapper() { return 1234L; } + public static float defaultFloatPrimitive() { + return 1.0f; + } + + public static Float defaultFloatWrapper() { + return 1.0f; + } + public static double defaultDoublePrimitive() { return 1.0; } @@ -329,6 +337,14 @@ public static Iterable defaultLongIterable() { return Arrays.asList(1L, 2L); } + public static float[] defaultFloatArray() { + return new float[] {1.0f, 2.0f}; + } + + public static Iterable defaultFloatIterable() { + return Arrays.asList(1.0f, 2.0f); + } + public static double[] defaultDoubleArray() { return new double[] {1.0, 2.0}; } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java index 5176013cf3..460c646807 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java @@ -185,6 +185,38 @@ public void int64WrapperNull() { assertEquals("NULL", v.getAsString()); } + @Test + public void float32() { + Value v = Value.float32(1.23f); + assertThat(v.getType()).isEqualTo(Type.float32()); + assertThat(v.isNull()).isFalse(); + assertThat(v.getFloat32()).isWithin(0.0001f).of(1.23f); + assertThat(v.toString()).isEqualTo("1.23"); + assertEquals("1.23", v.getAsString()); + assertEquals(Value.float32(Float.NaN), Value.float32(Float.NaN)); + } + + @Test + public void float32Wrapper() { + Value v = Value.float32(Float.valueOf(1.23f)); + assertThat(v.getType()).isEqualTo(Type.float32()); + assertThat(v.isNull()).isFalse(); + assertThat(v.getFloat32()).isWithin(0.0001f).of(1.23f); + assertThat(v.toString()).isEqualTo("1.23"); + assertEquals("1.23", v.getAsString()); + } + + @Test + public void float32WrapperNull() { + Value v = Value.float32(null); + assertThat(v.getType()).isEqualTo(Type.float32()); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat32); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + @Test public void float64() { Value v = Value.float64(1.23); @@ -193,6 +225,7 @@ public void float64() { assertThat(v.getFloat64()).isWithin(0.0001).of(1.23); assertThat(v.toString()).isEqualTo("1.23"); assertEquals("1.23", v.getAsString()); + assertEquals(Value.float64(Double.NaN), Value.float64(Double.NaN)); } @Test @@ -236,6 +269,7 @@ public void pgNumeric() { assertEquals(1234.5678D, value.getFloat64(), 0.00001); assertEquals("1234.5678", value.toString()); assertEquals("1234.5678", value.getAsString()); + assertEquals(Value.pgNumeric("NaN"), Value.pgNumeric("NaN")); } @Test @@ -863,6 +897,60 @@ public void int64ArrayNullTryGetBool() { assertThat(e.getMessage()).contains("Expected: BOOL actual: ARRAY"); } + @Test + public void float32Array() { + Value v = Value.float32Array(new float[] {.1f, .2f}); + assertThat(v.isNull()).isFalse(); + assertThat(v.getFloat32Array()).containsExactly(.1f, .2f).inOrder(); + assertThat(v.toString()).isEqualTo("[0.1,0.2]"); + assertEquals("[0.1,0.2]", v.getAsString()); + } + + @Test + public void float32ArrayRange() { + Value v = Value.float32Array(new float[] {.1f, .2f, .3f, .4f, .5f}, 1, 3); + assertThat(v.isNull()).isFalse(); + assertThat(v.getFloat32Array()).containsExactly(.2f, .3f, .4f).inOrder(); + assertThat(v.toString()).isEqualTo("[0.2,0.3,0.4]"); + assertEquals("[0.2,0.3,0.4]", v.getAsString()); + } + + @Test + public void float32ArrayNull() { + Value v = Value.float32Array((float[]) null); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat32Array); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + + @Test + public void float32ArrayWrapper() { + Value v = Value.float32Array(Arrays.asList(.1f, null, .3f)); + assertThat(v.isNull()).isFalse(); + assertThat(v.getFloat32Array()).containsExactly(.1f, null, .3f).inOrder(); + assertThat(v.toString()).isEqualTo("[0.1,NULL,0.3]"); + assertEquals("[0.1,NULL,0.3]", v.getAsString()); + } + + @Test + public void float32ArrayWrapperNull() { + Value v = Value.float32Array((Iterable) null); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat32Array); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + + @Test + public void float32ArrayTryGetFloat64Array() { + Value value = Value.float32Array(Collections.singletonList(.1f)); + IllegalStateException e = assertThrows(IllegalStateException.class, value::getFloat64Array); + assertThat(e.getMessage()).contains("Expected: ARRAY actual: ARRAY"); + } + @Test public void float64Array() { Value v = Value.float64Array(new double[] {.1, .2}); @@ -1426,6 +1514,13 @@ public void testValueToProto() { com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), Value.int64(null).toProto()); + assertEquals( + com.google.protobuf.Value.newBuilder().setNumberValue(3.14f).build(), + Value.float32(3.14f).toProto()); + assertEquals( + com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), + Value.float32(null).toProto()); + assertEquals( com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build(), Value.float64(3.14d).toProto()); @@ -1512,6 +1607,18 @@ public void testValueToProto() { .build()))) .build(), Value.int64Array(Arrays.asList(1L, null)).toProto()); + assertEquals( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + Arrays.asList( + com.google.protobuf.Value.newBuilder().setNumberValue(3.14f).build(), + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()))) + .build(), + Value.float32Array(Arrays.asList(3.14f, null)).toProto()); assertEquals( com.google.protobuf.Value.newBuilder() .setListValue( @@ -1667,6 +1774,29 @@ public void testValueToProto() { .build(), Value.struct(Struct.newBuilder().add(Value.int64Array(Arrays.asList(1L, null))).build()) .toProto()); + assertEquals( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + Arrays.asList( + com.google.protobuf.Value.newBuilder() + .setNumberValue(3.14f) + .build(), + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build())) + .build()) + .build()) + .build()) + .build(), + Value.struct( + Struct.newBuilder().add(Value.float32Array(Arrays.asList(3.14f, null))).build()) + .toProto()); assertEquals( com.google.protobuf.Value.newBuilder() .setListValue( @@ -1872,6 +2002,10 @@ public void testEqualsHashCode() { tester.addEqualityGroup(Value.int64(456)); tester.addEqualityGroup(Value.int64(null)); + tester.addEqualityGroup(Value.float32(1.23f), Value.float32(Float.valueOf(1.23f))); + tester.addEqualityGroup(Value.float32(4.56f)); + tester.addEqualityGroup(Value.float32(null)); + tester.addEqualityGroup(Value.float64(1.23), Value.float64(Double.valueOf(1.23))); tester.addEqualityGroup(Value.float64(4.56)); tester.addEqualityGroup(Value.float64(null)); @@ -1938,6 +2072,14 @@ public void testEqualsHashCode() { tester.addEqualityGroup(Value.int64Array(Collections.singletonList(3L))); tester.addEqualityGroup(Value.int64Array((Iterable) null)); + tester.addEqualityGroup( + Value.float32Array(Arrays.asList(.1f, .2f)), + Value.float32Array(new float[] {.1f, .2f}), + Value.float32Array(new float[] {.0f, .1f, .2f, .3f}, 1, 2), + Value.float32Array(plainIterable(.1f, .2f))); + tester.addEqualityGroup(Value.float32Array(Collections.singletonList(.3f))); + tester.addEqualityGroup(Value.float32Array((Iterable) null)); + tester.addEqualityGroup( Value.float64Array(Arrays.asList(.1, .2)), Value.float64Array(new double[] {.1, .2}), @@ -2009,6 +2151,11 @@ public void testGetAsString() { assertEquals(String.valueOf(Long.MAX_VALUE), Value.int64(Long.MAX_VALUE).getAsString()); assertEquals(String.valueOf(Long.MIN_VALUE), Value.int64(Long.MIN_VALUE).getAsString()); + assertEquals("3.14", Value.float32(3.14f).getAsString()); + assertEquals("NaN", Value.float32(Float.NaN).getAsString()); + assertEquals(String.valueOf(Float.MIN_VALUE), Value.float32(Float.MIN_VALUE).getAsString()); + assertEquals(String.valueOf(Float.MAX_VALUE), Value.float32(Float.MAX_VALUE).getAsString()); + assertEquals("3.14", Value.float64(3.14d).getAsString()); assertEquals("NaN", Value.float64(Double.NaN).getAsString()); assertEquals(String.valueOf(Double.MIN_VALUE), Value.float64(Double.MIN_VALUE).getAsString()); @@ -2052,6 +2199,9 @@ public void serialization() { reserializeAndAssert(Value.int64(123)); reserializeAndAssert(Value.int64(null)); + reserializeAndAssert(Value.float32(1.23f)); + reserializeAndAssert(Value.float32(null)); + reserializeAndAssert(Value.float64(1.23)); reserializeAndAssert(Value.float64(null)); @@ -2089,6 +2239,10 @@ public void serialization() { reserializeAndAssert(Value.int64Array(new long[] {1L, 2L})); reserializeAndAssert(Value.int64Array((Iterable) null)); + reserializeAndAssert(Value.float32Array(new float[] {.1f, .2f})); + reserializeAndAssert(Value.float32Array(BrokenSerializationList.of(.1f, .2f, .3f))); + reserializeAndAssert(Value.float32Array((Iterable) null)); + reserializeAndAssert(Value.float64Array(new double[] {.1, .2})); reserializeAndAssert(Value.float64Array(BrokenSerializationList.of(.1, .2, .3))); reserializeAndAssert(Value.float64Array((Iterable) null)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java index af67859b4a..9af443293a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java @@ -19,7 +19,6 @@ import com.google.cloud.spanner.ForceCloseSpannerFunction; import com.google.cloud.spanner.MockSpannerServiceImpl; import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; -import com.google.cloud.spanner.RandomResultSetGenerator; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.admin.database.v1.MockDatabaseAdminImpl; import com.google.cloud.spanner.admin.instance.v1.MockInstanceAdminImpl; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java new file mode 100644 index 0000000000..44ae730f8c --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java @@ -0,0 +1,683 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.connection; + +import static org.junit.Assert.*; + +import com.google.cloud.ByteArray; +import com.google.cloud.Date; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; +import com.google.cloud.spanner.Statement; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Value; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.Type; +import com.google.spanner.v1.TypeCode; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** Tests that all types can be read from Spanner and sent to Spanner. */ +@RunWith(Parameterized.class) +public class AllTypesMockServerTest extends AbstractMockServerTest { + + @Parameters(name = "dialect = {0}") + public static Object[] data() { + return Dialect.values(); + } + + @Parameter public Dialect dialect; + + private Dialect currentDialect; + + public static final Statement SELECT_STATEMENT = Statement.of("select * from all_types"); + + public static final boolean BOOL_VALUE = true; + public static final long INT64_VALUE = 1L; + public static final float FLOAT32_VALUE = 3.14f; + public static final double FLOAT64_VALUE = 3.14d; + public static final BigDecimal NUMERIC_VALUE = new BigDecimal("3.14"); + public static final String PG_NUMERIC_VALUE = "3.14"; + public static final String STRING_VALUE = "test-string"; + public static final String JSON_VALUE = "{\"key1\":\"value1\", \"key2\":\"value2\"}"; + public static final byte[] BYTES_VALUE = "test-bytes".getBytes(StandardCharsets.UTF_8); + public static final Date DATE_VALUE = Date.fromYearMonthDay(2024, 3, 2); + public static final Timestamp TIMESTAMP_VALUE = + Timestamp.parseTimestamp("2024-03-02T07:07:00.20982735Z"); + + public static final List BOOL_ARRAY_VALUE = Arrays.asList(true, null, false); + public static final List INT64_ARRAY_VALUE = + Arrays.asList(100L, null, 200L, Long.MIN_VALUE, Long.MAX_VALUE); + public static final List FLOAT32_ARRAY_VALUE = + Arrays.asList( + -3.14f, null, 6.626f, Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL, Float.NaN); + public static final List FLOAT64_ARRAY_VALUE = + Arrays.asList( + -3.14d, null, 6.626d, Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL, Double.NaN); + public static final List NUMERIC_ARRAY_VALUE = + Arrays.asList( + new BigDecimal("-3.14"), + null, + new BigDecimal("99.99"), + BigDecimal.ZERO, + new BigDecimal("1e-9"), + new BigDecimal("-9.9999999999999999999999999999999999999E+28"), + new BigDecimal("9.9999999999999999999999999999999999999E+28")); + public static final List PG_NUMERIC_ARRAY_VALUE = + Arrays.asList( + "-3.14", + null, + "99.99", + "NaN", + "1e-9", + "-9.9999999999999999999999999999999999999E+28", + "9.9999999999999999999999999999999999999E+28"); + public static final List STRING_ARRAY_VALUE = + Arrays.asList("test-string1", null, "test-string2"); + public static final List JSON_ARRAY_VALUE = + Arrays.asList( + "{\"key1\":\"value1.1\", \"key2\":\"value1.2\"}", + null, + "{\"key1\":\"value3.1\", \"key2\":\"value3.2\"}"); + public static final List BYTES_ARRAY_VALUE = + Arrays.asList(ByteArray.copyFrom("test-bytes1"), null, ByteArray.copyFrom("test-bytes2")); + public static final List DATE_ARRAY_VALUE = + Arrays.asList( + Date.fromYearMonthDay(2024, 3, 1), + null, + Date.fromYearMonthDay(2024, 3, 3), + Date.fromYearMonthDay(1, 1, 1), + Date.fromYearMonthDay(9999, 12, 31)); + public static final List TIMESTAMP_ARRAY_VALUE = + Arrays.asList( + Timestamp.parseTimestamp("2024-03-01T07:07:00.20982735Z"), + null, + Timestamp.parseTimestamp("2024-03-03T07:07:00Z"), + Timestamp.MIN_VALUE, + Timestamp.MAX_VALUE); + + @Before + public void setupDialect() { + if (currentDialect != dialect) { + mockSpanner.putStatementResult(StatementResult.detectDialectResult(dialect)); + setupAllTypesResultSet(dialect); + mockSpanner.putStatementResult(StatementResult.update(createInsertStatement(dialect), 1L)); + SpannerPool.closeSpannerPool(); + currentDialect = dialect; + } + } + + private void setupAllTypesResultSet(Dialect dialect) { + // Use RandomResultSetGenerator to generate metadata for a ResultSet with all types. + // This guarantees that this test will fail if a new type is added to RandomResultSetGenerator, + // but not added to this test. + // The columns in the result set are: + // COL1: BOOL + // COL2: INT64 + // COL3: FLOAT32 + // COL4: FLOAT64 + // COL5: NUMERIC / PG_NUMERIC + // COL6: STRING + // COL7: JSON / PG_JSONB + // COL8: BYTES + // COL9: DATE + // COL10: TIMESTAMP + // COL11-20: ARRAY<..> for the types above. + // Only for GoogleSQL: + // COL21: PROTO + // COL22: ENUM + // COL23: ARRAY + // COL24: ARRAY + ListValue.Builder row1Builder = + ListValue.newBuilder() + .addValues(Value.newBuilder().setBoolValue(BOOL_VALUE)) + .addValues(Value.newBuilder().setStringValue(String.valueOf(INT64_VALUE)).build()) + .addValues(Value.newBuilder().setNumberValue(FLOAT32_VALUE)) + .addValues(Value.newBuilder().setNumberValue(FLOAT64_VALUE)) + .addValues( + Value.newBuilder() + .setStringValue( + dialect == Dialect.POSTGRESQL + ? PG_NUMERIC_VALUE + : NUMERIC_VALUE.toEngineeringString())) + .addValues(Value.newBuilder().setStringValue(STRING_VALUE)) + .addValues(Value.newBuilder().setStringValue(JSON_VALUE)) + .addValues( + Value.newBuilder().setStringValue(Base64.getEncoder().encodeToString(BYTES_VALUE))) + .addValues(Value.newBuilder().setStringValue(DATE_VALUE.toString())) + .addValues(Value.newBuilder().setStringValue(TIMESTAMP_VALUE.toString())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + BOOL_ARRAY_VALUE.stream() + .map( + b -> + b == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder().setBoolValue(b).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + INT64_ARRAY_VALUE.stream() + .map( + l -> + l == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(String.valueOf(l)) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + FLOAT32_ARRAY_VALUE.stream() + .map( + f -> { + if (f == null) { + return Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build(); + } else if (Float.isNaN(f)) { + return Value.newBuilder().setStringValue("NaN").build(); + } else { + return Value.newBuilder().setNumberValue(f).build(); + } + }) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + FLOAT64_ARRAY_VALUE.stream() + .map( + d -> { + if (d == null) { + return Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build(); + } else if (Double.isNaN(d)) { + return Value.newBuilder().setStringValue("NaN").build(); + } else { + return Value.newBuilder().setNumberValue(d).build(); + } + }) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + dialect == Dialect.POSTGRESQL + ? PG_NUMERIC_ARRAY_VALUE.stream() + .map( + string -> + string == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(string) + .build()) + .collect(Collectors.toList()) + : NUMERIC_ARRAY_VALUE.stream() + .map( + bigDecimal -> + bigDecimal == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue( + bigDecimal.toEngineeringString()) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + STRING_ARRAY_VALUE.stream() + .map( + string -> + string == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder().setStringValue(string).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + JSON_ARRAY_VALUE.stream() + .map( + json -> + json == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder().setStringValue(json).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + BYTES_ARRAY_VALUE.stream() + .map( + byteArray -> + byteArray == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + byteArray.toByteArray())) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + DATE_ARRAY_VALUE.stream() + .map( + date -> + date == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(date.toString()) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + TIMESTAMP_ARRAY_VALUE.stream() + .map( + timestamp -> + timestamp == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(timestamp.toString()) + .build()) + .collect(Collectors.toList())) + .build())); + + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { + // Add PROTO values. + row1Builder + .addValues( + Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + SingerInfo.newBuilder() + .setSingerId(1L) + .setNationality("unknown") + .setBirthDate("1986-09-30") + .setGenre(Genre.POP) + .build() + .toByteArray())) + .build()) + .addValues(Value.newBuilder().setStringValue(String.valueOf(Genre.JAZZ_VALUE)).build()) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + SingerInfo.newBuilder() + .setSingerId(1L) + .setGenre(Genre.FOLK) + .setBirthDate("200-01-01") + .setNationality("no") + .build() + .toByteArray())) + .build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE)) + .addValues( + Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + SingerInfo.newBuilder() + .setSingerId(2L) + .setGenre(Genre.JAZZ) + .setBirthDate("200-01-02") + .setNationality("dk") + .build() + .toByteArray())) + .build()) + .build()) + .build()) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Genre.ROCK_VALUE)) + .build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE)) + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Genre.ROCK_VALUE)) + .build()) + .build()) + .build()); + } + + com.google.spanner.v1.ResultSet resultSet = + com.google.spanner.v1.ResultSet.newBuilder() + .setMetadata( + RandomResultSetGenerator.generateAllTypesMetadata( + RandomResultSetGenerator.generateAllTypes(dialect))) + .addRows(row1Builder.build()) + .build(); + mockSpanner.putStatementResults(StatementResult.query(SELECT_STATEMENT, resultSet)); + } + + public static Statement createInsertStatement(Dialect dialect) { + Statement.Builder builder = Statement.newBuilder("insert into all_types ("); + builder.append( + IntStream.rangeClosed(1, RandomResultSetGenerator.generateAllTypes(dialect).length) + .mapToObj(col -> "COL" + col) + .collect(Collectors.joining(", ", "", ") values ("))); + builder.append( + IntStream.rangeClosed(1, RandomResultSetGenerator.generateAllTypes(dialect).length) + .mapToObj(col -> "@p" + col) + .collect(Collectors.joining(", ", "", ")"))); + int param = 0; + return builder + .bind("p" + ++param) + .to(BOOL_VALUE) + .bind("p" + ++param) + .to(INT64_VALUE) + .bind("p" + ++param) + .to(FLOAT32_VALUE) + .bind("p" + ++param) + .to(FLOAT64_VALUE) + .bind("p" + ++param) + .to( + dialect == Dialect.POSTGRESQL + ? com.google.cloud.spanner.Value.pgNumeric(PG_NUMERIC_VALUE) + : com.google.cloud.spanner.Value.numeric(NUMERIC_VALUE)) + .bind("p" + ++param) + .to(STRING_VALUE) + .bind("p" + ++param) + .to( + dialect == Dialect.POSTGRESQL + ? com.google.cloud.spanner.Value.pgJsonb(JSON_VALUE) + : com.google.cloud.spanner.Value.json(JSON_VALUE)) + .bind("p" + ++param) + .to(ByteArray.copyFrom(BYTES_VALUE)) + .bind("p" + ++param) + .to(DATE_VALUE) + .bind("p" + ++param) + .to(TIMESTAMP_VALUE) + .bind("p" + ++param) + .toBoolArray(BOOL_ARRAY_VALUE) + .bind("p" + ++param) + .toInt64Array(INT64_ARRAY_VALUE) + .bind("p" + ++param) + .toFloat32Array(FLOAT32_ARRAY_VALUE) + .bind("p" + ++param) + .toFloat64Array(FLOAT64_ARRAY_VALUE) + .bind("p" + ++param) + .to( + dialect == Dialect.POSTGRESQL + ? com.google.cloud.spanner.Value.pgNumericArray(PG_NUMERIC_ARRAY_VALUE) + : com.google.cloud.spanner.Value.numericArray(NUMERIC_ARRAY_VALUE)) + .bind("p" + ++param) + .toStringArray(STRING_ARRAY_VALUE) + .bind("p" + ++param) + .to( + dialect == Dialect.POSTGRESQL + ? com.google.cloud.spanner.Value.pgJsonbArray(JSON_ARRAY_VALUE) + : com.google.cloud.spanner.Value.jsonArray(JSON_ARRAY_VALUE)) + .bind("p" + ++param) + .toBytesArray(BYTES_ARRAY_VALUE) + .bind("p" + ++param) + .toDateArray(DATE_ARRAY_VALUE) + .bind("p" + ++param) + .toTimestampArray(TIMESTAMP_ARRAY_VALUE) + .build(); + } + + @After + public void clearRequests() { + mockSpanner.clearRequests(); + } + + @Test + public void testSelectAllTypes() { + try (Connection connection = createConnection()) { + try (ResultSet resultSet = connection.executeQuery(SELECT_STATEMENT)) { + assertTrue(resultSet.next()); + + int col = -1; + assertEquals(BOOL_VALUE, resultSet.getBoolean(++col)); + assertEquals(INT64_VALUE, resultSet.getLong(++col)); + assertEquals(FLOAT32_VALUE, resultSet.getFloat(++col), 0.0f); + assertEquals(FLOAT64_VALUE, resultSet.getDouble(++col), 0.0d); + if (dialect == Dialect.POSTGRESQL) { + assertEquals(PG_NUMERIC_VALUE, resultSet.getString(++col)); + } else { + assertEquals(NUMERIC_VALUE, resultSet.getBigDecimal(++col)); + } + assertEquals(STRING_VALUE, resultSet.getString(++col)); + assertEquals( + JSON_VALUE, + dialect == Dialect.POSTGRESQL ? resultSet.getPgJsonb(++col) : resultSet.getJson(++col)); + assertArrayEquals(BYTES_VALUE, resultSet.getBytes(++col).toByteArray()); + assertEquals(DATE_VALUE, resultSet.getDate(++col)); + assertEquals(TIMESTAMP_VALUE, resultSet.getTimestamp(++col)); + + assertEquals(BOOL_ARRAY_VALUE, resultSet.getBooleanList(++col)); + assertEquals(INT64_ARRAY_VALUE, resultSet.getLongList(++col)); + assertEquals(FLOAT32_ARRAY_VALUE, resultSet.getFloatList(++col)); + assertEquals(FLOAT64_ARRAY_VALUE, resultSet.getDoubleList(++col)); + if (dialect == Dialect.POSTGRESQL) { + assertEquals(PG_NUMERIC_ARRAY_VALUE, resultSet.getStringList(++col)); + } else { + assertEquals(NUMERIC_ARRAY_VALUE, resultSet.getBigDecimalList(++col)); + } + assertEquals(STRING_ARRAY_VALUE, resultSet.getStringList(++col)); + assertEquals( + JSON_ARRAY_VALUE, + dialect == Dialect.POSTGRESQL + ? resultSet.getPgJsonbList(++col) + : resultSet.getJsonList(++col)); + assertEquals(BYTES_ARRAY_VALUE, resultSet.getBytesList(++col)); + assertEquals(DATE_ARRAY_VALUE, resultSet.getDateList(++col)); + assertEquals(TIMESTAMP_ARRAY_VALUE, resultSet.getTimestampList(++col)); + + assertFalse(resultSet.next()); + } + } + } + + @Test + public void testInsertAllTypes() { + try (Connection connection = createConnection()) { + assertEquals(1L, connection.executeUpdate(createInsertStatement(dialect))); + + assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)); + ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0); + Map paramTypes = request.getParamTypesMap(); + Map params = request.getParams().getFieldsMap(); + assertEquals(20, paramTypes.size()); + assertEquals(20, params.size()); + + // Verify param types. + ImmutableList expectedTypes = + ImmutableList.of( + TypeCode.BOOL, + TypeCode.INT64, + TypeCode.FLOAT32, + TypeCode.FLOAT64, + TypeCode.NUMERIC, + TypeCode.STRING, + TypeCode.JSON, + TypeCode.BYTES, + TypeCode.DATE, + TypeCode.TIMESTAMP); + for (int col = 0; col < expectedTypes.size(); col++) { + assertEquals(expectedTypes.get(col), paramTypes.get("p" + (col + 1)).getCode()); + int arrayCol = col + expectedTypes.size(); + assertEquals(TypeCode.ARRAY, paramTypes.get("p" + (arrayCol + 1)).getCode()); + assertEquals( + expectedTypes.get(col), + paramTypes.get("p" + (arrayCol + 1)).getArrayElementType().getCode()); + } + + // Verify param values. + int col = 0; + assertEquals(BOOL_VALUE, params.get("p" + ++col).getBoolValue()); + assertEquals(String.valueOf(INT64_VALUE), params.get("p" + ++col).getStringValue()); + assertEquals(FLOAT32_VALUE, params.get("p" + ++col).getNumberValue(), 0.0d); + assertEquals(FLOAT64_VALUE, params.get("p" + ++col).getNumberValue(), 0.0d); + assertEquals( + dialect == Dialect.POSTGRESQL ? PG_NUMERIC_VALUE : NUMERIC_VALUE.toEngineeringString(), + params.get("p" + ++col).getStringValue()); + assertEquals(STRING_VALUE, params.get("p" + ++col).getStringValue()); + assertEquals(JSON_VALUE, params.get("p" + ++col).getStringValue()); + assertEquals( + Base64.getEncoder().encodeToString(BYTES_VALUE), + params.get("p" + ++col).getStringValue()); + assertEquals(DATE_VALUE.toString(), params.get("p" + ++col).getStringValue()); + assertEquals(TIMESTAMP_VALUE.toString(), params.get("p" + ++col).getStringValue()); + + assertEquals( + BOOL_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : value.getBoolValue()) + .collect(Collectors.toList())); + assertEquals( + INT64_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : Long.valueOf(value.getStringValue())) + .collect(Collectors.toList())); + assertEquals( + FLOAT32_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : (float) value.getNumberValue()) + .collect(Collectors.toList())); + assertEquals( + FLOAT64_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : value.getNumberValue()) + .collect(Collectors.toList())); + if (dialect == Dialect.POSTGRESQL) { + assertEquals( + PG_NUMERIC_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : value.getStringValue()) + .collect(Collectors.toList())); + } else { + assertEquals( + NUMERIC_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : new BigDecimal(value.getStringValue())) + .collect(Collectors.toList())); + } + assertEquals( + STRING_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : value.getStringValue()) + .collect(Collectors.toList())); + assertEquals( + JSON_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : value.getStringValue()) + .collect(Collectors.toList())); + assertEquals( + BYTES_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map( + value -> + value.hasNullValue() ? null : ByteArray.fromBase64(value.getStringValue())) + .collect(Collectors.toList())); + assertEquals( + DATE_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : Date.parseDate(value.getStringValue())) + .collect(Collectors.toList())); + assertEquals( + TIMESTAMP_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map( + value -> + value.hasNullValue() + ? null + : Timestamp.parseTimestamp(value.getStringValue())) + .collect(Collectors.toList())); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java index 4e8fb0cfcb..759f058aa0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java @@ -57,6 +57,8 @@ public class ChecksumResultSetTest { .to(2 * 2) .set("doubleVal") .to(Value.float64(3.14d * 2d)) + .set("floatVal") + .to(Value.float32(3.14f * 3f)) .set("bigDecimalVal") .to(Value.numeric(BigDecimal.valueOf(123 * 2, 2))) .set("pgNumericVal") @@ -83,6 +85,8 @@ public class ChecksumResultSetTest { .to(Value.int64Array(Arrays.asList(2L, null, 1L, 0L))) .set("doubleArray") .to(Value.float64Array(Arrays.asList(3.14d, null, 6.6626d, 10.1d))) + .set("floatArray") + .to(Value.float32Array(Arrays.asList(2.71f, null, 6.6626f, 10.1f))) .set("bigDecimalArray") .to(Value.numericArray(Arrays.asList(BigDecimal.TEN, null, BigDecimal.ONE))) .set("pgNumericArray") @@ -128,6 +132,7 @@ public void testRetry() { Type.StructField.of("boolVal", Type.bool()), Type.StructField.of("longVal", Type.int64()), Type.StructField.of("doubleVal", Type.float64()), + Type.StructField.of("floatVal", Type.float32()), Type.StructField.of("bigDecimalVal", Type.numeric()), Type.StructField.of("pgNumericVal", Type.pgNumeric()), Type.StructField.of("stringVal", Type.string()), @@ -143,6 +148,7 @@ public void testRetry() { Type.StructField.of("boolArray", Type.array(Type.bool())), Type.StructField.of("longArray", Type.array(Type.int64())), Type.StructField.of("doubleArray", Type.array(Type.float64())), + Type.StructField.of("floatArray", Type.array(Type.float32())), Type.StructField.of("bigDecimalArray", Type.array(Type.numeric())), Type.StructField.of("pgNumericArray", Type.array(Type.pgNumeric())), Type.StructField.of("byteArray", Type.array(Type.bytes())), @@ -164,6 +170,8 @@ public void testRetry() { .to(2) .set("doubleVal") .to(Value.float64(3.14d)) + .set("floatVal") + .to(Value.float32(2.71f)) .set("bigDecimalVal") .to(Value.numeric(BigDecimal.valueOf(123, 2))) .set("pgNumericVal") @@ -190,6 +198,8 @@ public void testRetry() { .to(Value.int64Array(Arrays.asList(1L, null, 2L))) .set("doubleArray") .to(Value.float64Array(Arrays.asList(3.14d, null, 6.6626d))) + .set("floatArray") + .to(Value.float32Array(Arrays.asList(2.71f, null, 6.6626f))) .set("bigDecimalArray") .to(Value.numericArray(Arrays.asList(BigDecimal.ONE, null, BigDecimal.TEN))) .set("pgNumericArray") @@ -238,6 +248,8 @@ public void testRetry() { .to((Long) null) .set("doubleVal") .to((Double) null) + .set("floatVal") + .to((Float) null) .set("bigDecimalVal") .to((BigDecimal) null) .set("pgNumericVal") @@ -264,6 +276,8 @@ public void testRetry() { .toInt64Array((Iterable) null) .set("doubleArray") .toFloat64Array((Iterable) null) + .set("floatArray") + .toFloat32Array((Iterable) null) .set("bigDecimalArray") .toNumericArray(null) .set("pgNumericArray") diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java index 0b54b35225..015927440d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java @@ -1863,6 +1863,50 @@ public void testCheckResultTypeAllowed() { checkResultTypeAllowed(parser.parse(Statement.of(start)), allowedResultTypes); } + @Test + public void testSetRetryAbortsInternally() { + try (ConnectionImpl connection = + createConnection( + ConnectionOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setUri(URI) + .build())) { + assertFalse("Read-only should be disabled by default", connection.isReadOnly()); + assertTrue("Autocommit should be enabled by default", connection.isAutocommit()); + assertFalse( + "Retry aborts internally should be disabled by default on test connections", + connection.isRetryAbortsInternally()); + + // It should be possible to change this value also when in auto-commit mode. + connection.setRetryAbortsInternally(true); + assertTrue(connection.isRetryAbortsInternally()); + + // It should be possible to change this value also when in transactional mode, as long as + // there is no active transaction. + connection.setAutocommit(false); + connection.setRetryAbortsInternally(false); + assertFalse(connection.isRetryAbortsInternally()); + + // It should be possible to change the value when in read-only mode. + connection.setReadOnly(true); + connection.setRetryAbortsInternally(true); + assertTrue(connection.isRetryAbortsInternally()); + + // It should not be possible to change the value when there is an active transaction. + connection.setReadOnly(false); + connection.setAutocommit(false); + connection.execute(Statement.of(SELECT)); + assertThrows(SpannerException.class, () -> connection.setRetryAbortsInternally(false)); + // Verify that the value did not change. + assertTrue(connection.isRetryAbortsInternally()); + + // Rolling back the connection should allow us to set the property again. + connection.rollback(); + connection.setRetryAbortsInternally(false); + assertFalse(connection.isRetryAbortsInternally()); + } + } + private void assertThrowResultNotAllowed( AbstractStatementParser parser, String sql, ImmutableSet allowedResultTypes) { SpannerException exception = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MergedResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MergedResultSetTest.java index 3cf717e176..8a309115c7 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MergedResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MergedResultSetTest.java @@ -150,7 +150,7 @@ public void testAllResultsAreReturned() { if (numPartitions == 0) { assertEquals(0, resultSet.getColumnCount()); } else { - assertEquals(22, resultSet.getColumnCount()); + assertEquals(24, resultSet.getColumnCount()); assertEquals(Type.bool(), resultSet.getColumnType(0)); assertEquals(Type.bool(), resultSet.getColumnType("COL0")); assertEquals(10, resultSet.getColumnIndex("COL10")); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/PartitionedQueryMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/PartitionedQueryMockServerTest.java index e5731ff0f6..2536b74f10 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/PartitionedQueryMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/PartitionedQueryMockServerTest.java @@ -58,15 +58,20 @@ public static Object[] data() { @Parameter public Dialect dialect; + private Dialect currentDialect; + @Before public void setupDialect() { - mockSpanner.putStatementResult(StatementResult.detectDialectResult(dialect)); + if (currentDialect != dialect) { + mockSpanner.putStatementResult(StatementResult.detectDialectResult(dialect)); + SpannerPool.closeSpannerPool(); + currentDialect = dialect; + } } @After public void clearRequests() { mockSpanner.clearRequests(); - SpannerPool.closeSpannerPool(); } @Test @@ -349,9 +354,9 @@ public void testRunEmptyPartitionedQuery() { statement, PartitionOptions.newBuilder().setMaxPartitions(maxPartitions).build())) { assertFalse(resultSet.next()); assertNotNull(resultSet.getMetadata()); - assertEquals(22, resultSet.getMetadata().getRowType().getFieldsCount()); + assertEquals(24, resultSet.getMetadata().getRowType().getFieldsCount()); assertNotNull(resultSet.getType()); - assertEquals(22, resultSet.getType().getStructFields().size()); + assertEquals(24, resultSet.getType().getStructFields().size()); } } assertEquals(1, mockSpanner.countRequestsOfType(CreateSessionRequest.class)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java index 2067d36b5e..ef38df0db3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java @@ -51,6 +51,7 @@ public static Type[] generateAllTypes(Dialect dialect) { Arrays.asList( Type.newBuilder().setCode(TypeCode.BOOL).build(), Type.newBuilder().setCode(TypeCode.INT64).build(), + Type.newBuilder().setCode(TypeCode.FLOAT32).build(), Type.newBuilder().setCode(TypeCode.FLOAT64).build(), dialect == Dialect.POSTGRESQL ? Type.newBuilder() @@ -76,6 +77,10 @@ public static Type[] generateAllTypes(Dialect dialect) { .setCode(TypeCode.ARRAY) .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64)) .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT32)) + .build(), Type.newBuilder() .setCode(TypeCode.ARRAY) .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) @@ -229,6 +234,13 @@ private void setRandomValue(Value.Builder builder, Type type) { random.nextInt(2019) + 1, random.nextInt(11) + 1, random.nextInt(28) + 1); builder.setStringValue(date.toString()); break; + case FLOAT32: + if (randomNaN()) { + builder.setNumberValue(Float.NaN); + } else { + builder.setNumberValue(random.nextFloat()); + } + break; case FLOAT64: if (randomNaN()) { builder.setNumberValue(Double.NaN); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java index ed68f05c5f..b11c1f19be 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java @@ -39,6 +39,8 @@ import com.google.cloud.spanner.connection.SpannerPool.SpannerPoolKey; import com.google.common.base.Ticker; import com.google.common.testing.FakeTicker; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.util.concurrent.TimeUnit; @@ -69,6 +71,10 @@ public class SpannerPoolTest { private ConnectionOptions options7 = mock(ConnectionOptions.class); private ConnectionOptions options8 = mock(ConnectionOptions.class); + private ConnectionOptions optionsOpenTelemetry1 = mock(ConnectionOptions.class); + private ConnectionOptions optionsOpenTelemetry2 = mock(ConnectionOptions.class); + private ConnectionOptions optionsOpenTelemetry3 = mock(ConnectionOptions.class); + private SpannerPool createSubjectAndMocks() { return createSubjectAndMocks(0L, Ticker.systemTicker()); } @@ -83,6 +89,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { } }; + OpenTelemetry openTelemetry1 = OpenTelemetrySdk.builder().build(); + OpenTelemetry openTelemetry2 = OpenTelemetrySdk.builder().build(); + when(options1.getCredentialsUrl()).thenReturn(credentials1); when(options1.getProjectId()).thenReturn("test-project-1"); when(options2.getCredentialsUrl()).thenReturn(credentials2); @@ -101,6 +110,13 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { when(options8.getProjectId()).thenReturn("test-project-3"); when(options8.isRouteToLeader()).thenReturn(false); + when(optionsOpenTelemetry1.getProjectId()).thenReturn("test-project-1"); + when(optionsOpenTelemetry1.getOpenTelemetry()).thenReturn(openTelemetry1); + when(optionsOpenTelemetry2.getProjectId()).thenReturn("test-project-1"); + when(optionsOpenTelemetry2.getOpenTelemetry()).thenReturn(openTelemetry1); + when(optionsOpenTelemetry3.getProjectId()).thenReturn("test-project-1"); + when(optionsOpenTelemetry3.getOpenTelemetry()).thenReturn(openTelemetry2); + return pool; } @@ -498,4 +514,21 @@ public void testSpannerPoolKeyEquality() { assertEquals(key3, key4); assertNotEquals(key4, key5); } + + @Test + public void testOpenTelemetry() { + SpannerPool pool = createSubjectAndMocks(); + Spanner spanner1; + Spanner spanner2; + + // assert equal + spanner1 = pool.getSpanner(optionsOpenTelemetry1, connection1); + spanner2 = pool.getSpanner(optionsOpenTelemetry2, connection2); + assertEquals(spanner1, spanner2); + + // assert not equal + spanner1 = pool.getSpanner(optionsOpenTelemetry1, connection1); + spanner2 = pool.getSpanner(optionsOpenTelemetry3, connection2); + assertNotEquals(spanner1, spanner2); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncExamplesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncExamplesTest.java index 5a22c64009..87f26da904 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncExamplesTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncExamplesTest.java @@ -344,9 +344,12 @@ public void readOnlyTransaction() throws Exception { values2 = rs.toListAsync(input -> input.getString("StringValue"), executor); } } + + ApiFuture>> allAsListValues = + ApiFutures.allAsList(Arrays.asList(values1, values2)); ApiFuture> allValues = ApiFutures.transform( - ApiFutures.allAsList(Arrays.asList(values1, values2)), + allAsListValues, input -> Iterables.mergeSorted( input, Comparator.comparing(o -> Integer.valueOf(o.substring(1)))), diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITFloat32Test.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITFloat32Test.java new file mode 100644 index 0000000000..de2640b86c --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITFloat32Test.java @@ -0,0 +1,415 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.it; + +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.Key; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.ResultSet; +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.connection.ConnectionOptions; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@Category(ParallelIntegrationTest.class) +@RunWith(Parameterized.class) +public class ITFloat32Test { + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + + @Parameterized.Parameters(name = "Dialect = {0}") + public static List data() { + return Arrays.asList( + new DialectTestParameter(Dialect.GOOGLE_STANDARD_SQL), + new DialectTestParameter(Dialect.POSTGRESQL)); + } + + @Parameterized.Parameter() public DialectTestParameter dialect; + + private static DatabaseClient googleStandardSQLClient; + private static DatabaseClient postgreSQLClient; + + private static final String[] GOOGLE_STANDARD_SQL_SCHEMA = + new String[] { + "CREATE TABLE T (" + + " Key STRING(MAX) NOT NULL," + + " Float32Value FLOAT32," + + " Float32ArrayValue ARRAY," + + ") PRIMARY KEY (Key)" + }; + + private static final String[] POSTGRESQL_SCHEMA = + new String[] { + "CREATE TABLE T (" + + " Key VARCHAR PRIMARY KEY," + + " Float32Value REAL," + + " Float32ArrayValue REAL[]" + + ")" + }; + + private static DatabaseClient client; + + private static boolean isUsingCloudDevel() { + String jobType = System.getenv("JOB_TYPE"); + + // Assumes that the jobType contains the string "cloud-devel" to signal that + // the environment is cloud-devel. + return !isNullOrEmpty(jobType) && jobType.contains("cloud-devel"); + } + + @BeforeClass + public static void setUpDatabase() + throws ExecutionException, InterruptedException, TimeoutException { + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + + Database googleStandardSQLDatabase = + env.getTestHelper().createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA); + + googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); + + Database postgreSQLDatabase = + env.getTestHelper() + .createTestDatabase(Dialect.POSTGRESQL, Arrays.asList(POSTGRESQL_SCHEMA)); + postgreSQLClient = env.getTestHelper().getDatabaseClient(postgreSQLDatabase); + } + + @Before + public void before() { + client = + dialect.dialect == Dialect.GOOGLE_STANDARD_SQL ? googleStandardSQLClient : postgreSQLClient; + } + + @AfterClass + public static void tearDown() throws Exception { + ConnectionOptions.closeSpanner(); + } + + /** Sequence used to generate unique keys. */ + private static int seq; + + private static String uniqueString() { + return String.format("k%04d", seq++); + } + + private String lastKey; + + private Timestamp write(Mutation m) { + return client.write(Collections.singletonList(m)); + } + + private Mutation.WriteBuilder baseInsert() { + return Mutation.newInsertOrUpdateBuilder("T").set("Key").to(lastKey = uniqueString()); + } + + private Struct readRow(String table, String key, String... columns) { + return client + .singleUse(TimestampBound.strong()) + .readRow(table, Key.of(key), Arrays.asList(columns)); + } + + private Struct readLastRow(String... columns) { + return readRow("T", lastKey, columns); + } + + @Test + public void writeFloat32() { + write(baseInsert().set("Float32Value").to(2.0f).build()); + Struct row = readLastRow("Float32Value"); + assertFalse(row.isNull(0)); + assertEquals(2.0f, row.getFloat(0), 0.0f); + } + + @Test + public void writeFloat32NonNumbers() { + + write(baseInsert().set("Float32Value").to(Float.NEGATIVE_INFINITY).build()); + Struct row = readLastRow("Float32Value"); + assertFalse(row.isNull(0)); + assertEquals(Float.NEGATIVE_INFINITY, row.getFloat(0), 0.0f); + + write(baseInsert().set("Float32Value").to(Float.POSITIVE_INFINITY).build()); + row = readLastRow("Float32Value"); + assertFalse(row.isNull(0)); + assertEquals(Float.POSITIVE_INFINITY, row.getFloat(0), 0.0); + + write(baseInsert().set("Float32Value").to(Float.NaN).build()); + row = readLastRow("Float32Value"); + assertFalse(row.isNull(0)); + assertTrue(Float.isNaN(row.getFloat(0))); + } + + @Test + public void writeFloat32Null() { + write(baseInsert().set("Float32Value").to((Float) null).build()); + Struct row = readLastRow("Float32Value"); + assertTrue(row.isNull(0)); + } + + @Test + public void writeFloat32ArrayNull() { + write(baseInsert().set("Float32ArrayValue").toFloat32Array((float[]) null).build()); + Struct row = readLastRow("Float32ArrayValue"); + assertTrue(row.isNull(0)); + } + + @Test + public void writeFloat32ArrayEmpty() { + write(baseInsert().set("Float32ArrayValue").toFloat32Array(new float[] {}).build()); + Struct row = readLastRow("Float32ArrayValue"); + assertFalse(row.isNull(0)); + assertTrue(row.getFloatList(0).isEmpty()); + } + + @Test + public void writeFloat32Array() { + write( + baseInsert() + .set("Float32ArrayValue") + .toFloat32Array(Arrays.asList(null, 1.0f, 2.0f)) + .build()); + Struct row = readLastRow("Float32ArrayValue"); + assertFalse(row.isNull(0)); + assertEquals(row.getFloatList(0), Arrays.asList(null, 1.0f, 2.0f)); + assertThrows(NullPointerException.class, () -> row.getFloatArray(0)); + } + + @Test + public void writeFloat32ArrayNoNulls() { + write(baseInsert().set("Float32ArrayValue").toFloat32Array(Arrays.asList(1.0f, 2.0f)).build()); + Struct row = readLastRow("Float32ArrayValue"); + assertFalse(row.isNull(0)); + assertEquals(2, row.getFloatArray(0).length); + assertEquals(1.0f, row.getFloatArray(0)[0], 0.0f); + assertEquals(2.0f, row.getFloatArray(0)[1], 0.0f); + } + + private String getInsertStatementWithLiterals() { + String statement = "INSERT INTO T (Key, Float32Value, Float32ArrayValue) VALUES "; + + if (dialect.dialect == Dialect.POSTGRESQL) { + statement += + "('dml1', 3.14::float8, array[1.1]::float4[]), " + + "('dml2', '3.14'::float4, array[3.14::float4, 3.14::float8]::float4[]), " + + "('dml3', 'nan'::real, array['inf'::real, (3.14::float8)::float4, 1.2, '-inf']::float4[]), " + + "('dml4', 1.175494e-38::real, array[1.175494e-38, 3.4028234e38, -3.4028234e38]::real[]), " + + "('dml5', null, null)"; + } else { + statement += + "('dml1', 3.14, [CAST(1.1 AS FLOAT32)]), " + + "('dml2', CAST('3.14' AS FLOAT32), array[CAST(3.14 AS FLOAT32), 3.14]), " + + "('dml3', CAST('nan' AS FLOAT32), array[CAST('inf' AS FLOAT32), CAST(CAST(3.14 AS FLOAT64) AS FLOAT32), 1.2, CAST('-inf' AS FLOAT32)]), " + + "('dml4', 1.175494e-38, [CAST(1.175494e-38 AS FLOAT32), 3.4028234e38, -3.4028234e38]), " + + "('dml5', null, null)"; + } + return statement; + } + + @Test + public void float32Literals() { + client + .readWriteTransaction() + .run( + transaction -> { + transaction.executeUpdate(Statement.of(getInsertStatementWithLiterals())); + return null; + }); + + verifyContents("dml"); + } + + private String getInsertStatementWithParameters() { + String pgStatement = + "INSERT INTO T (Key, Float32Value, Float32ArrayValue) VALUES " + + "('param1', $1, $2), " + + "('param2', $3, $4), " + + "('param3', $5, $6), " + + "('param4', $7, $8), " + + "('param5', $9, $10)"; + + return (dialect.dialect == Dialect.POSTGRESQL) ? pgStatement : pgStatement.replace("$", "@p"); + } + + @Test + public void float32Parameter() { + client + .readWriteTransaction() + .run( + transaction -> { + transaction.executeUpdate( + Statement.newBuilder(getInsertStatementWithParameters()) + .bind("p1") + .to(Value.float32(3.14f)) + .bind("p2") + .to(Value.float32Array(Arrays.asList(1.1f))) + .bind("p3") + .to(Value.float32(3.14f)) + .bind("p4") + .to(Value.float32Array(new float[] {3.14f, 3.14f})) + .bind("p5") + .to(Value.float32(Float.NaN)) + .bind("p6") + .to( + Value.float32Array( + Arrays.asList( + Float.POSITIVE_INFINITY, 3.14f, 1.2f, Float.NEGATIVE_INFINITY))) + .bind("p7") + .to(Value.float32(Float.MIN_NORMAL)) + .bind("p8") + .to( + Value.float32Array( + Arrays.asList( + Float.MIN_NORMAL, Float.MAX_VALUE, -1 * Float.MAX_VALUE))) + .bind("p9") + .to(Value.float32(null)) + .bind("p10") + .to(Value.float32Array((float[]) null)) + .build()); + return null; + }); + + verifyContents("param"); + } + + private String getInsertStatementForUntypedParameters() { + if (dialect.dialect == Dialect.POSTGRESQL) { + return "INSERT INTO T (key, float32value, float32arrayvalue) VALUES " + + "('untyped1', ($1)::float4, ($2)::float4[])"; + } + return "INSERT INTO T (Key, Float32Value, Float32ArrayValue) VALUES " + + "('untyped1', CAST(@p1 AS FLOAT32), CAST(@p2 AS ARRAY))"; + } + + @Test + public void float32UntypedParameter() { + client + .readWriteTransaction() + .run( + transaction -> { + transaction.executeUpdate( + Statement.newBuilder(getInsertStatementForUntypedParameters()) + .bind("p1") + .to( + Value.untyped( + com.google.protobuf.Value.newBuilder() + .setNumberValue((double) 3.14f) + .build())) + .bind("p2") + .to( + Value.untyped( + com.google.protobuf.Value.newBuilder() + .setListValue( + com.google.protobuf.ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue((double) Float.MIN_NORMAL))) + .build())) + .build()); + return null; + }); + + Struct row = readRow("T", "untyped1", "Float32Value", "Float32ArrayValue"); + // Float32Value + assertFalse(row.isNull(0)); + assertEquals(3.14f, row.getFloat(0), 0.00001f); + // Float32ArrayValue + assertFalse(row.isNull(1)); + assertEquals(1, row.getFloatList(1).size()); + assertEquals(Float.MIN_NORMAL, row.getFloatList(1).get(0), 0.00001f); + } + + private void verifyContents(String keyPrefix) { + try (ResultSet resultSet = + client + .singleUse() + .executeQuery( + Statement.of( + "SELECT Key AS key, Float32Value AS float32value, Float32ArrayValue AS float32arrayvalue FROM T WHERE Key LIKE '{keyPrefix}%' ORDER BY key" + .replace("{keyPrefix}", keyPrefix)))) { + + assertTrue(resultSet.next()); + + assertEquals(3.14f, resultSet.getFloat("float32value"), 0.00001f); + assertEquals(Value.float32(3.14f), resultSet.getValue("float32value")); + + assertArrayEquals(new float[] {1.1f}, resultSet.getFloatArray("float32arrayvalue"), 0.00001f); + + assertTrue(resultSet.next()); + + assertEquals(3.14f, resultSet.getFloat("float32value"), 0.00001f); + assertEquals(Arrays.asList(3.14f, 3.14f), resultSet.getFloatList("float32arrayvalue")); + assertEquals( + Value.float32Array(new float[] {3.14f, 3.14f}), resultSet.getValue("float32arrayvalue")); + + assertTrue(resultSet.next()); + assertTrue(Float.isNaN(resultSet.getFloat("float32value"))); + assertTrue(Float.isNaN(resultSet.getValue("float32value").getFloat32())); + assertEquals( + Arrays.asList(Float.POSITIVE_INFINITY, 3.14f, 1.2f, Float.NEGATIVE_INFINITY), + resultSet.getFloatList("float32arrayvalue")); + assertEquals( + Value.float32Array( + Arrays.asList(Float.POSITIVE_INFINITY, 3.14f, 1.2f, Float.NEGATIVE_INFINITY)), + resultSet.getValue("float32arrayvalue")); + + assertTrue(resultSet.next()); + assertEquals(Float.MIN_NORMAL, resultSet.getFloat("float32value"), 0.00001f); + assertEquals(Float.MIN_NORMAL, resultSet.getValue("float32value").getFloat32(), 0.00001f); + assertEquals(3, resultSet.getFloatList("float32arrayvalue").size()); + assertEquals(Float.MIN_NORMAL, resultSet.getFloatList("float32arrayvalue").get(0), 0.00001); + assertEquals(Float.MAX_VALUE, resultSet.getFloatList("float32arrayvalue").get(1), 0.00001f); + assertEquals( + -1 * Float.MAX_VALUE, resultSet.getFloatList("float32arrayvalue").get(2), 0.00001f); + assertEquals(3, resultSet.getValue("float32arrayvalue").getFloat32Array().size()); + + assertTrue(resultSet.next()); + assertTrue(resultSet.isNull("float32value")); + assertTrue(resultSet.isNull("float32arrayvalue")); + + assertFalse(resultSet.next()); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java index a691fbf78b..170ce75e69 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner.it; import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; @@ -254,6 +255,36 @@ public void bindInt64Null() { assertThat(row.isNull(0)).isTrue(); } + // TODO: Remove once FLOAT32 is supported in production. + private static boolean isUsingCloudDevel() { + String jobType = System.getenv("JOB_TYPE"); + + // Assumes that the jobType contains the string "cloud-devel" to signal that + // the environment is cloud-devel. + return !isNullOrEmpty(jobType) && jobType.contains("cloud-devel"); + } + + @Test + public void bindFloat32() { + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + + Struct row = + execute(Statement.newBuilder(selectValueQuery).bind("p1").to(2.0f), Type.float32()); + assertThat(row.isNull(0)).isFalse(); + assertThat(row.getFloat(0)).isWithin(0.0f).of(2.0f); + } + + @Test + public void bindFloat32Null() { + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + + Struct row = + execute(Statement.newBuilder(selectValueQuery).bind("p1").to((Float) null), Type.float32()); + assertThat(row.isNull(0)).isTrue(); + } + @Test public void bindFloat64() { Struct row = execute(Statement.newBuilder(selectValueQuery).bind("p1").to(2.0), Type.float64()); @@ -497,6 +528,58 @@ public void bindInt64ArrayNull() { assertThat(row.isNull(0)).isTrue(); } + @Test + public void bindFloat32Array() { + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + + Struct row = + execute( + Statement.newBuilder(selectValueQuery) + .bind("p1") + .toFloat32Array( + asList( + null, + 1.0f, + 2.0f, + Float.NEGATIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.NaN)), + Type.array(Type.float32())); + assertThat(row.isNull(0)).isFalse(); + assertThat(row.getFloatList(0)) + .containsExactly( + null, 1.0f, 2.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NaN) + .inOrder(); + } + + @Test + public void bindFloat32ArrayEmpty() { + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + + Struct row = + execute( + Statement.newBuilder(selectValueQuery) + .bind("p1") + .toFloat32Array(Collections.emptyList()), + Type.array(Type.float32())); + assertThat(row.isNull(0)).isFalse(); + assertThat(row.getFloatList(0)).containsExactly(); + } + + @Test + public void bindFloat32ArrayNull() { + assumeFalse("Emulator does not support FLOAT32 yet", isUsingEmulator()); + assumeTrue("FLOAT32 is currently only supported in cloud-devel", isUsingCloudDevel()); + + Struct row = + execute( + Statement.newBuilder(selectValueQuery).bind("p1").toFloat32Array((float[]) null), + Type.array(Type.float32())); + assertThat(row.isNull(0)).isTrue(); + } + @Test public void bindFloat64Array() { Struct row = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java index 2cfb9cbbc5..ea60b9fb64 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionTest.java @@ -298,7 +298,6 @@ public void readAbort() throws Exception { } catch (SpannerException e) { if (e.getErrorCode() == ErrorCode.ABORTED) { assertThat(e).isInstanceOf(AbortedException.class); - assertThat(e.getRetryDelayInMillis()).isNotEqualTo(-1L); } throw new RuntimeException("Swallowed exception: " + e.getMessage()); } @@ -338,7 +337,6 @@ public void readAbort() throws Exception { } catch (SpannerException e) { if (e.getErrorCode() == ErrorCode.ABORTED) { assertThat(e).isInstanceOf(AbortedException.class); - assertThat(e.getRetryDelayInMillis()).isNotEqualTo(-1L); } throw new RuntimeException("Swallowed exception: " + e.getMessage()); } diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index fe0aae9d55..e55efda7d9 100644 --- a/grpc-google-cloud-spanner-admin-database-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 grpc-google-cloud-spanner-admin-database-v1 GRPC library for grpc-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index 7d4e7ec962..aead92c9a1 100644 --- a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 grpc-google-cloud-spanner-admin-instance-v1 GRPC library for grpc-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/grpc-google-cloud-spanner-executor-v1/pom.xml b/grpc-google-cloud-spanner-executor-v1/pom.xml index 5d508b1cc2..cc007bd888 100644 --- a/grpc-google-cloud-spanner-executor-v1/pom.xml +++ b/grpc-google-cloud-spanner-executor-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-executor-v1 - 6.60.1 + 6.61.0 grpc-google-cloud-spanner-executor-v1 GRPC library for google-cloud-spanner com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 399a04ef7b..941536483c 100644 --- a/grpc-google-cloud-spanner-v1/pom.xml +++ b/grpc-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/pom.xml b/pom.xml index ef95e28fbf..bb0fb156d0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.60.1 + 6.61.0 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.25.0 + 3.27.0 @@ -61,47 +61,47 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-executor-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-executor-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 com.google.cloud google-cloud-spanner - 6.60.1 + 6.61.0 @@ -121,7 +121,7 @@ com.google.truth truth - 1.4.1 + 1.4.2 test diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index 0514be006b..5e6f00a6fc 100644 --- a/proto-google-cloud-spanner-admin-database-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.60.1 + 6.61.0 proto-google-cloud-spanner-admin-database-v1 PROTO library for proto-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 9fe5dad3a6..205dfdcc1b 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.60.1 + 6.61.0 proto-google-cloud-spanner-admin-instance-v1 PROTO library for proto-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/proto-google-cloud-spanner-executor-v1/pom.xml b/proto-google-cloud-spanner-executor-v1/pom.xml index 4e560822d1..e8dcec3683 100644 --- a/proto-google-cloud-spanner-executor-v1/pom.xml +++ b/proto-google-cloud-spanner-executor-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-executor-v1 - 6.60.1 + 6.61.0 proto-google-cloud-spanner-executor-v1 Proto library for google-cloud-spanner com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index dafe631172..7c9f074e92 100644 --- a/proto-google-cloud-spanner-v1/pom.xml +++ b/proto-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.60.1 + 6.61.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.60.1 + 6.61.0 diff --git a/samples/README.md b/samples/README.md index 9f961354d9..914c455b46 100644 --- a/samples/README.md +++ b/samples/README.md @@ -34,7 +34,7 @@ You can run a given `ClassName` via: ## Tutorial ### Running the tutorial - mvn exec:java -Dexec.mainClass=com.example.spanner.SpannerSample -Dexec.args=" my-instance my-database" + mvn exec:java -Dexec.mainClass=com.example.spanner.admin.archived.SpannerSample -Dexec.args=" my-instance my-database" ## Tracing sample `TracingSample.java` demonstrates how to export traces generated by client library to StackDriver and to /tracez page. diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index af217d9bd4..fb290694d6 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -33,7 +33,7 @@ com.google.cloud google-cloud-spanner - 6.60.0 + 6.60.1 @@ -100,7 +100,7 @@ com.google.truth truth - 1.4.1 + 1.4.2 test @@ -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/native-image/pom.xml b/samples/native-image/pom.xml index 0c72603f1a..ea90374c40 100644 --- a/samples/native-image/pom.xml +++ b/samples/native-image/pom.xml @@ -51,7 +51,7 @@ com.google.truth truth - 1.4.1 + 1.4.2 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index cc31362f1a..298e4eab43 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -32,7 +32,7 @@ com.google.cloud google-cloud-spanner - 6.60.1 + 6.61.0 @@ -99,7 +99,7 @@ com.google.truth truth - 1.4.1 + 1.4.2 test @@ -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 5dbe22f146..0d149d8e78 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -111,7 +111,7 @@ com.google.truth truth - 1.4.1 + 1.4.2 test @@ -165,7 +165,7 @@ false - com.example.spanner.SpannerSample + com.example.spanner.admin.archived.SpannerSample true lib/ @@ -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/AddAndDropDatabaseRole.java b/samples/snippets/src/main/java/com/example/spanner/AddAndDropDatabaseRole.java index 2d65974099..75e85f5f85 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AddAndDropDatabaseRole.java +++ b/samples/snippets/src/main/java/com/example/spanner/AddAndDropDatabaseRole.java @@ -17,12 +17,12 @@ package com.example.spanner; // [START spanner_add_and_drop_database_role] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -34,54 +34,46 @@ static void addAndDropDatabaseRole() { String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - String parentRole = "my-new-parent-role"; - String childRole = "my-new-child-role"; + String parentRole = "parent_role"; + String childRole = "child_role"; addAndDropDatabaseRole(projectId, instanceId, databaseId, parentRole, childRole); } static void addAndDropDatabaseRole( - String projectId, String instanceId, String databaseId, String parentRole, String childRole) { + String projectId, String instanceId, String databaseId, + String parentRole, String childRole) { try (Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - OperationFuture operation = - adminClient.updateDatabaseDdl( - instanceId, - databaseId, + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + System.out.println("Waiting for role create operation to complete..."); + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), ImmutableList.of( - "CREATE ROLE " + parentRole, - "GRANT SELECT ON TABLE Albums TO ROLE " + parentRole, - "CREATE ROLE " + childRole, - "GRANT ROLE " + parentRole + " TO ROLE " + childRole), - null); - try { - System.out.println("Waiting for role create operation to complete..."); - operation.get(5, TimeUnit.MINUTES); - System.out.printf( - "Created roles %s and %s and granted privileges%n", parentRole, childRole); - // Delete role and membership. - operation = - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of( - "REVOKE ROLE " + parentRole + " FROM ROLE " + childRole, - "DROP ROLE " + childRole), - null); - System.out.println("Waiting for role revoke & drop operation to complete..."); - operation.get(5, TimeUnit.MINUTES); - System.out.printf("Revoked privileges and dropped role %s%n", childRole); - } catch (ExecutionException | TimeoutException e) { - System.out.printf( - "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage()); - e.printStackTrace(); - } catch (InterruptedException e) { - System.out.println( - "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted"); - } + String.format("CREATE ROLE %s", parentRole), + String.format("GRANT SELECT ON TABLE Albums TO ROLE %s", parentRole), + String.format("CREATE ROLE %s", childRole), + String.format("GRANT ROLE %s TO ROLE %s", parentRole, childRole))) + .get(5, TimeUnit.MINUTES); + System.out.printf( + "Created roles %s and %s and granted privileges%n", parentRole, childRole); + // Delete role and membership. + System.out.println("Waiting for role revoke & drop operation to complete..."); + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of( + String.format("REVOKE ROLE %s FROM ROLE %s", parentRole, childRole), + String.format("DROP ROLE %s", childRole))).get(5, TimeUnit.MINUTES); + System.out.printf("Revoked privileges and dropped role %s%n", childRole); + } catch (ExecutionException | TimeoutException e) { + System.out.printf( + "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage()); + e.printStackTrace(); + } catch (InterruptedException e) { + System.out.println( + "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted"); } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java index 8be919ddad..c87b25ff47 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java @@ -17,12 +17,12 @@ package com.example.spanner; // [START spanner_add_json_column] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; class AddJsonColumnSample { @@ -33,25 +33,24 @@ static void addJsonColumn() throws InterruptedException, ExecutionException { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - addJsonColumn(adminClient, instanceId, databaseId); - } + addJsonColumn(projectId, instanceId, databaseId); } - static void addJsonColumn(DatabaseAdminClient adminClient, String instanceId, String databaseId) + static void addJsonColumn(String projectId, String instanceId, String databaseId) throws InterruptedException, ExecutionException { - OperationFuture operation = - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSON"), - null); - // Wait for the operation to finish. - // This will throw an ExecutionException if the operation fails. - operation.get(); - System.out.printf("Successfully added column `VenueDetails`%n"); + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // Wait for the operation to finish. + // This will throw an ExecutionException if the operation fails. + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSON")).get(); + System.out.printf("Successfully added column `VenueDetails`%n"); + } } } // [END spanner_add_json_column] diff --git a/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java index e8152c4fa0..ab2607c498 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java @@ -17,12 +17,12 @@ package com.example.spanner; // [START spanner_postgresql_jsonb_add_column] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; class AddJsonbColumnSample { @@ -33,25 +33,25 @@ static void addJsonbColumn() throws InterruptedException, ExecutionException { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - addJsonbColumn(adminClient, instanceId, databaseId); - } + addJsonbColumn(projectId, instanceId, databaseId); } - static void addJsonbColumn(DatabaseAdminClient adminClient, String instanceId, String databaseId) + static void addJsonbColumn(String projectId, String instanceId, String databaseId) throws InterruptedException, ExecutionException { - OperationFuture operation = - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"), - null); - // Wait for the operation to finish. - // This will throw an ExecutionException if the operation fails. - operation.get(); - System.out.printf("Successfully added column `VenueDetails`%n"); + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // JSONB datatype is only supported with PostgreSQL-dialect databases. + // Wait for the operation to finish. + // This will throw an ExecutionException if the operation fails. + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB")).get(); + System.out.printf("Successfully added column `VenueDetails`%n"); + } } } // [END spanner_postgresql_jsonb_add_column] diff --git a/samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java index 7d29cdf15d..00cfb848e7 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java @@ -17,12 +17,12 @@ package com.example.spanner; // [START spanner_add_numeric_column] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; class AddNumericColumnSample { @@ -33,26 +33,24 @@ static void addNumericColumn() throws InterruptedException, ExecutionException { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - addNumericColumn(adminClient, instanceId, databaseId); - } + addNumericColumn(projectId, instanceId, databaseId); } - static void addNumericColumn( - DatabaseAdminClient adminClient, String instanceId, String databaseId) + static void addNumericColumn(String projectId, String instanceId, String databaseId) throws InterruptedException, ExecutionException { - OperationFuture operation = - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of("ALTER TABLE Venues ADD COLUMN Revenue NUMERIC"), - null); - // Wait for the operation to finish. - // This will throw an ExecutionException if the operation fails. - operation.get(); - System.out.printf("Successfully added column `Revenue`%n"); + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // Wait for the operation to finish. + // This will throw an ExecutionException if the operation fails. + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of("ALTER TABLE Venues ADD COLUMN Revenue NUMERIC")).get(); + System.out.printf("Successfully added column `Revenue`%n"); + } } } // [END spanner_add_numeric_column] diff --git a/samples/snippets/src/main/java/com/example/spanner/AlterSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/AlterSequenceSample.java index 6b9014a26f..641449ace9 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AlterSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/AlterSequenceSample.java @@ -17,7 +17,7 @@ package com.example.spanner; // [START spanner_alter_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,13 +25,16 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class AlterSequenceSample { + static void alterSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -42,17 +45,14 @@ static void alterSequence() { static void alterSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + databaseAdminClient + .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId), ImmutableList.of( "ALTER SEQUENCE Seq SET OPTIONS " - + "(skip_range_min = 1000, skip_range_max = 5000000)"), - null) + + "(skip_range_min = 1000, skip_range_max = 5000000)")) .get(5, TimeUnit.MINUTES); System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSample.java index 1caf26fb28..6950e6a4fa 100644 --- a/samples/snippets/src/main/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSample.java @@ -17,10 +17,12 @@ package com.example.spanner; // [START spanner_alter_table_with_foreign_key_delete_cascade] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; class AlterTableWithForeignKeyDeleteCascadeSample { @@ -30,30 +32,28 @@ static void alterForeignKeyDeleteCascadeConstraint() { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - alterForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); - } + alterForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); } static void alterForeignKeyDeleteCascadeConstraint( - DatabaseAdminClient adminClient, String instanceId, String databaseId) { - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of( - "ALTER TABLE ShoppingCarts\n" - + " ADD CONSTRAINT FKShoppingCartsCustomerName\n" - + " FOREIGN KEY (CustomerName)\n" - + " REFERENCES Customers(CustomerName)\n" - + " ON DELETE CASCADE\n"), - null); - System.out.printf( - String.format( - "Altered ShoppingCarts table with FKShoppingCartsCustomerName\n" - + "foreign key constraint on database %s on instance %s", - databaseId, instanceId)); + String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient.updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, + databaseId), + ImmutableList.of( + "ALTER TABLE ShoppingCarts\n" + + " ADD CONSTRAINT FKShoppingCartsCustomerName\n" + + " FOREIGN KEY (CustomerName)\n" + + " REFERENCES Customers(CustomerName)\n" + + " ON DELETE CASCADE\n")); + System.out.printf( + String.format( + "Altered ShoppingCarts table with FKShoppingCartsCustomerName\n" + + "foreign key constraint on database %s on instance %s", + databaseId, instanceId)); + } } } // [END spanner_alter_table_with_foreign_key_delete_cascade] diff --git a/samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java b/samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java index 795edcb15a..c6ee706687 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java @@ -18,84 +18,80 @@ // [START spanner_copy_backup] -import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.Timestamp; -import com.google.cloud.spanner.Backup; -import com.google.cloud.spanner.BackupId; -import com.google.cloud.spanner.DatabaseAdminClient; 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.spanner.admin.database.v1.CopyBackupMetadata; -import java.time.LocalDateTime; +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.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() { // 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 (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { copyBackup(databaseAdminClient, projectId, instanceId, sourceBackupId, destinationBackupId); } } static void copyBackup( - DatabaseAdminClient databaseAdminClient, - String projectId, - String instanceId, - String sourceBackupId, - String destinationBackupId) { + 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)); - // Creates a copy of an existing backup. - Backup destinationBackup = - databaseAdminClient - .newBackupBuilder(BackupId.of(projectId, instanceId, destinationBackupId)) - .setExpireTime(expireTime) - .build(); + 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 [" + destinationBackup.getId() + "]..."); - OperationFuture operation = - databaseAdminClient.copyBackup( - BackupId.of(projectId, instanceId, sourceBackupId), destinationBackup); + System.out.println("Copying backup [" + destinationBackupId + "]..."); + Backup destinationBackup; try { + // Creates a copy of an existing backup. // Wait for the backup operation to complete. - destinationBackup = operation.get(); - System.out.println("Copied backup [" + destinationBackup.getId() + "]"); + 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 = destinationBackup.reload(); + 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.getId().getName(), - destinationBackup.getSize(), - LocalDateTime.ofEpochSecond( - destinationBackup.getProto().getCreateTime().getSeconds(), - destinationBackup.getProto().getCreateTime().getNanos(), - OffsetDateTime.now().getOffset()), - LocalDateTime.ofEpochSecond( - destinationBackup.getProto().getVersionTime().getSeconds(), - destinationBackup.getProto().getVersionTime().getNanos(), - OffsetDateTime.now().getOffset()))); - return; + 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/CreateBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/CreateBackupWithEncryptionKey.java index b2a00ae2ad..e2c7b17061 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateBackupWithEncryptionKey.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateBackupWithEncryptionKey.java @@ -18,17 +18,18 @@ // [START spanner_create_backup_with_encryption_key] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.Timestamp; -import com.google.cloud.spanner.Backup; -import com.google.cloud.spanner.BackupId; -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; -import com.google.cloud.spanner.encryption.EncryptionConfigs; -import com.google.spanner.admin.database.v1.CreateBackupMetadata; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +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.CreateBackupRequest; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.InstanceName; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -37,7 +38,7 @@ public class CreateBackupWithEncryptionKey { - static void createBackupWithEncryptionKey() throws InterruptedException { + static void createBackupWithEncryptionKey() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; @@ -47,8 +48,8 @@ static void createBackupWithEncryptionKey() throws InterruptedException { "projects/" + projectId + "/locations//keyRings//cryptoKeys/"; try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient adminClient = spanner.createDatabaseAdminClient()) { createBackupWithEncryptionKey( adminClient, projectId, @@ -60,24 +61,29 @@ static void createBackupWithEncryptionKey() throws InterruptedException { } static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient, - String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName) - throws InterruptedException { + String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName) { // Set expire time to 14 days from now. - final Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); - final Backup backupToCreate = adminClient - .newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) - .setDatabase(DatabaseId.of(projectId, instanceId, databaseId)) - .setExpireTime(expireTime) - .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName)) - .build(); - final OperationFuture operation = adminClient - .createBackup(backupToCreate); + 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(); - Backup backup; + 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 = operation.get(1200, TimeUnit.SECONDS); + 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()); @@ -89,14 +95,13 @@ static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient, // 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.getId().getName(), - backup.getSize(), + backup.getName(), + backup.getSizeBytes(), LocalDateTime.ofEpochSecond( - backup.getProto().getCreateTime().getSeconds(), - backup.getProto().getCreateTime().getNanos(), + backup.getCreateTime().getSeconds(), + backup.getCreateTime().getNanos(), OffsetDateTime.now().getOffset()), kmsKeyName ); diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java index fa3354dd9c..33917685cd 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java @@ -1,11 +1,11 @@ /* - * Copyright 2021 Google LLC + * Copyright 2023 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 + * 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, @@ -18,60 +18,51 @@ //[START spanner_create_database_with_default_leader] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; -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.spanner.admin.database.v1.CreateDatabaseMetadata; -import java.util.Arrays; +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 java.io.IOException; import java.util.concurrent.ExecutionException; public class CreateDatabaseWithDefaultLeaderSample { - static void createDatabaseWithDefaultLeader() { + static void createDatabaseWithDefaultLeader() throws IOException { // TODO(developer): Replace these variables before running the sample. - final String projectId = "my-project"; - final String instanceId = "my-instance"; - final String databaseId = "my-database"; + final String instanceName = "projects/my-project/instances/my-instance-id"; + final String databaseId = "my-database-name"; final String defaultLeader = "my-default-leader"; - createDatabaseWithDefaultLeader(projectId, instanceId, databaseId, defaultLeader); + createDatabaseWithDefaultLeader(instanceName, databaseId, defaultLeader); } - static void createDatabaseWithDefaultLeader( - String projectId, String instanceId, String databaseId, String defaultLeader) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); - final OperationFuture operation = databaseAdminClient - .createDatabase( - instanceId, - databaseId, - Arrays.asList( - "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", - "ALTER DATABASE " + "`" + databaseId + "`" - + " SET OPTIONS ( default_leader = '" + defaultLeader + "' )" - ) - ); - final Database database = operation.get(); - System.out.println("Created database [" + database.getId() + "]"); - System.out.println("\tDefault leader: " + database.getDefaultLeader()); + static void createDatabaseWithDefaultLeader(String instanceName, String databaseId, + String defaultLeader) throws IOException { + try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) { + Database createdDatabase = + databaseAdminClient.createDatabaseAsync( + CreateDatabaseRequest.newBuilder() + .setParent(instanceName) + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .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", + "ALTER DATABASE " + "`" + databaseId + "`" + + " SET OPTIONS ( default_leader = '" + defaultLeader + "' )")) + .build()).get(); + System.out.println("Created database [" + createdDatabase.getName() + "]"); + System.out.println("\tDefault leader: " + createdDatabase.getDefaultLeader()); } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. throw (SpannerException) e.getCause(); @@ -82,4 +73,4 @@ static void createDatabaseWithDefaultLeader( } } } -//[END spanner_create_database_with_default_leader] +//[END spanner_create_database_with_default_leader] \ No newline at end of file diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java index 7d3850933b..c06e9c3eba 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java @@ -18,16 +18,15 @@ // [START spanner_create_database_with_encryption_key] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; -import com.google.cloud.spanner.encryption.EncryptionConfigs; -import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; -import java.util.Arrays; +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.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -43,8 +42,8 @@ static void createDatabaseWithEncryptionKey() { "projects/" + projectId + "/locations//keyRings//cryptoKeys/"; try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient adminClient = spanner.createDatabaseAdminClient()) { createDatabaseWithEncryptionKey( adminClient, projectId, @@ -56,32 +55,35 @@ static void createDatabaseWithEncryptionKey() { static void createDatabaseWithEncryptionKey(DatabaseAdminClient adminClient, String projectId, String instanceId, String databaseId, String kmsKeyName) { - final Database databaseToCreate = adminClient - .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) - .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(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(); - final OperationFuture operation = adminClient - .createDatabase(databaseToCreate, Arrays.asList( - "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" - )); try { System.out.println("Waiting for operation to complete..."); - Database createdDatabase = operation.get(120, TimeUnit.SECONDS); + Database createdDatabase = + adminClient.createDatabaseAsync(request).get(120, TimeUnit.SECONDS); System.out.printf( "Database %s created with encryption key %s%n", - createdDatabase.getId(), + createdDatabase.getName(), createdDatabase.getEncryptionConfig().getKmsKeyName() ); } catch (ExecutionException e) { diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSample.java b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSample.java index 1eaa7c403d..888fe62588 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSample.java @@ -18,15 +18,15 @@ // [START spanner_create_database_with_version_retention_period] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; 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.spanner.admin.database.v1.CreateDatabaseMetadata; -import java.util.Arrays; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.common.collect.Lists; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.InstanceName; import java.util.concurrent.ExecutionException; public class CreateDatabaseWithVersionRetentionPeriodSample { @@ -38,39 +38,25 @@ static void createDatabaseWithVersionRetentionPeriod() { String databaseId = "my-database"; String versionRetentionPeriod = "7d"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - createDatabaseWithVersionRetentionPeriod(adminClient, instanceId, databaseId, - versionRetentionPeriod); - } + createDatabaseWithVersionRetentionPeriod(projectId, instanceId, databaseId, + versionRetentionPeriod); } - static void createDatabaseWithVersionRetentionPeriod(DatabaseAdminClient adminClient, + static void createDatabaseWithVersionRetentionPeriod(String projectId, String instanceId, String databaseId, String versionRetentionPeriod) { - OperationFuture op = - adminClient.createDatabase( - instanceId, - databaseId, - Arrays.asList( - "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", - "ALTER DATABASE " + "`" + databaseId + "`" - + " SET OPTIONS ( version_retention_period = '" + versionRetentionPeriod + "' )" - )); - try { - Database database = op.get(); - System.out.println("Created database [" + database.getId() + "]"); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setParent(InstanceName.of(projectId, instanceId).toString()) + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .addAllExtraStatements(Lists.newArrayList("ALTER DATABASE " + "`" + databaseId + "`" + + " SET OPTIONS ( version_retention_period = '" + versionRetentionPeriod + "' )")) + .build(); + Database database = + databaseAdminClient.createDatabaseAsync(request).get(); + System.out.println("Created database [" + database.getName() + "]"); System.out.println("\tVersion retention period: " + database.getVersionRetentionPeriod()); System.out.println("\tEarliest version time: " + database.getEarliestVersionTime()); } catch (ExecutionException e) { diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceConfigSample.java index 379e0e2617..426d7c0484 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * 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. @@ -17,53 +17,73 @@ package com.example.spanner; // [START spanner_create_instance_config] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.InstanceConfigId; -import com.google.cloud.spanner.InstanceConfigInfo; -import com.google.cloud.spanner.ReplicaInfo; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; -import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.CreateInstanceConfigRequest; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.ProjectName; +import com.google.spanner.admin.instance.v1.ReplicaInfo; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; class CreateInstanceConfigSample { + static void createInstanceConfig() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; - String baseInstanceConfig = "my-base-instance-config"; + String baseInstanceConfigId = "nam11"; String instanceConfigId = "custom-instance-config4"; - createInstanceConfig(projectId, baseInstanceConfig, instanceConfigId); + + createInstanceConfig(projectId, baseInstanceConfigId, instanceConfigId); } static void createInstanceConfig( - String projectId, String baseInstanceConfig, String instanceConfigId) { + String projectId, String baseInstanceConfigId, String instanceConfigId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - final InstanceConfig baseConfig = instanceAdminClient.getInstanceConfig(baseInstanceConfig); - List readOnlyReplicas = - ImmutableList.of(baseConfig.getOptionalReplicas().get(0)); - InstanceConfigInfo instanceConfigInfo = - InstanceConfig.newBuilder(InstanceConfigId.of(projectId, instanceConfigId), baseConfig) - .setDisplayName(instanceConfigId) - .addReadOnlyReplicas(readOnlyReplicas) - .build(); - final OperationFuture operation = - instanceAdminClient.createInstanceConfig(instanceConfigInfo); + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final InstanceConfigName baseInstanceConfigName = InstanceConfigName.of(projectId, + baseInstanceConfigId); + final InstanceConfig baseConfig = + instanceAdminClient.getInstanceConfig(baseInstanceConfigName.toString()); + final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, + instanceConfigId); + /** + * The replicas for the custom instance configuration must include all the replicas of the + * base configuration, in addition to at least one from the list of optional replicas of the + * base configuration. + */ + final List replicas = + Stream.concat(baseConfig.getReplicasList().stream(), + baseConfig.getOptionalReplicasList().stream().limit(1)).collect(Collectors.toList()); + final InstanceConfig instanceConfig = + InstanceConfig.newBuilder().setName(instanceConfigName.toString()) + .setBaseConfig(baseInstanceConfigName.toString()) + .setDisplayName("Instance Configuration").addAllReplicas(replicas).build(); + final CreateInstanceConfigRequest createInstanceConfigRequest = + CreateInstanceConfigRequest.newBuilder().setParent(ProjectName.of(projectId).toString()) + .setInstanceConfigId(instanceConfigId).setInstanceConfig(instanceConfig).build(); try { - System.out.printf("Waiting for create operation for %s to complete...\n", instanceConfigId); - InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES); - System.out.printf("Created instance configuration %s\n", instanceConfig.getId()); + System.out.printf("Waiting for create operation for %s to complete...\n", + instanceConfigName); + InstanceConfig instanceConfigResult = + instanceAdminClient.createInstanceConfigAsync( + createInstanceConfigRequest).get(5, TimeUnit.MINUTES); + System.out.printf("Created instance configuration %s\n", instanceConfigResult.getName()); } catch (ExecutionException | TimeoutException e) { System.out.printf( "Error: Creating instance configuration %s failed with error message %s\n", - instanceConfigInfo.getId(), e.getMessage()); + instanceConfig.getName(), e.getMessage()); } catch (InterruptedException e) { System.out.println( "Error: Waiting for createInstanceConfig operation to finish was interrupted"); diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceExample.java b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceExample.java index 44e36a8b27..b53727ba6d 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,14 @@ package com.example.spanner; //[START spanner_create_instance] -import com.google.api.gax.longrunning.OperationFuture; -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.Spanner; import com.google.cloud.spanner.SpannerOptions; -import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.CreateInstanceRequest; +import com.google.spanner.admin.instance.v1.Instance; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.ProjectName; import java.util.concurrent.ExecutionException; class CreateInstanceExample { @@ -38,36 +37,41 @@ static void createInstance() { } static void createInstance(String projectId, String instanceId) { - Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); - InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - // Set Instance configuration. - String configId = "regional-us-central1"; int nodeCount = 2; String displayName = "Descriptive name"; - // Create an InstanceInfo object that will be used to create the instance. - InstanceInfo instanceInfo = - InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId)) - .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) - .setNodeCount(nodeCount) + // Create an Instance object that will be used to create the instance. + Instance instance = + Instance.newBuilder() .setDisplayName(displayName) + .setNodeCount(nodeCount) + .setConfig( + InstanceConfigName.of(projectId, "regional-us-central1").toString()) .build(); - OperationFuture operation = - instanceAdminClient.createInstance(instanceInfo); - try { + + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + // Wait for the createInstance operation to finish. - Instance instance = operation.get(); - System.out.printf("Instance %s was successfully created%n", instance.getId()); + Instance createdInstance = instanceAdminClient.createInstanceAsync( + CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of(projectId).toString()) + .setInstanceId(instanceId) + .setInstance(instance) + .build()).get(); + System.out.printf("Instance %s was successfully created%n", createdInstance.getName()); } catch (ExecutionException e) { System.out.printf( "Error: Creating instance %s failed with error message %s%n", - instanceInfo.getId(), e.getMessage()); + instance.getName(), e.getMessage()); } catch (InterruptedException e) { System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); - } finally { - spanner.close(); } } } -//[END spanner_create_instance] +//[END spanner_create_instance] \ No newline at end of file diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithAutoscalingConfigExample.java b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithAutoscalingConfigExample.java index 0719857411..dc62dd7a68 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithAutoscalingConfigExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithAutoscalingConfigExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * 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. @@ -18,16 +18,14 @@ // [START spanner_create_instance_with_autoscaling_config] -import com.google.api.gax.longrunning.OperationFuture; -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.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; import com.google.spanner.admin.instance.v1.AutoscalingConfig; -import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; +import com.google.spanner.admin.instance.v1.CreateInstanceRequest; +import com.google.spanner.admin.instance.v1.Instance; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.ProjectName; import java.util.concurrent.ExecutionException; class CreateInstanceWithAutoscalingConfigExample { @@ -40,44 +38,55 @@ static void createInstance() { } static void createInstance(String projectId, String instanceId) { - Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); - InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + // Set Instance configuration. + String configId = "regional-us-central1"; + String displayName = "Descriptive name"; - // Set Instance configuration. - String configId = "regional-us-central1"; - // Create an autoscaling config. - AutoscalingConfig autoscalingConfig = - AutoscalingConfig.newBuilder() - .setAutoscalingLimits( - AutoscalingConfig.AutoscalingLimits.newBuilder().setMinNodes(1).setMaxNodes(2)) - .setAutoscalingTargets( - AutoscalingConfig.AutoscalingTargets.newBuilder() - .setHighPriorityCpuUtilizationPercent(65) - .setStorageUtilizationPercent(95)) - .build(); + // Create an autoscaling config. + // When autoscaling_config is enabled, node_count and processing_units fields + // need not be specified. + AutoscalingConfig autoscalingConfig = + AutoscalingConfig.newBuilder() + .setAutoscalingLimits( + AutoscalingConfig.AutoscalingLimits.newBuilder().setMinNodes(1).setMaxNodes(2)) + .setAutoscalingTargets( + AutoscalingConfig.AutoscalingTargets.newBuilder() + .setHighPriorityCpuUtilizationPercent(65) + .setStorageUtilizationPercent(95)) + .build(); + Instance instance = + Instance.newBuilder() + .setAutoscalingConfig(autoscalingConfig) + .setDisplayName(displayName) + .setConfig( + InstanceConfigName.of(projectId, configId).toString()) + .build(); - // Create an InstanceInfo object that will be used to create the instance. - InstanceInfo instanceInfo = - InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId)) - .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) - .setAutoscalingConfig(autoscalingConfig) - .setDisplayName("Descriptive name") - .build(); - OperationFuture operation = - instanceAdminClient.createInstance(instanceInfo); - - try { - // Wait for the createInstance operation to finish. - Instance instance = operation.get(); - System.out.printf("Autoscaler instance %s was successfully created%n", instance.getId()); - } catch (ExecutionException e) { - System.out.printf( - "Error: Creating instance %s failed with error message %s%n", - instanceInfo.getId(), e.getMessage()); - } catch (InterruptedException e) { - System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); - } finally { - spanner.close(); + // Creates a new instance + System.out.printf("Creating instance %s.%n", instanceId); + try { + // Wait for the createInstance operation to finish. + Instance instanceResult = instanceAdminClient.createInstanceAsync( + CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of(projectId).toString()) + .setInstanceId(instanceId) + .setInstance(instance) + .build()).get(); + System.out.printf("Autoscaler instance %s was successfully created%n", + instanceResult.getName()); + } catch (ExecutionException e) { + System.out.printf( + "Error: Creating instance %s failed with error message %s%n", + instance.getName(), e.getMessage()); + } catch (InterruptedException e) { + System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); + } } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithProcessingUnitsExample.java b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithProcessingUnitsExample.java index 1bfc66d3fd..293c10249c 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithProcessingUnitsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateInstanceWithProcessingUnitsExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * 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. @@ -18,15 +18,13 @@ //[START spanner_create_instance_with_processing_units] -import com.google.api.gax.longrunning.OperationFuture; -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.Spanner; import com.google.cloud.spanner.SpannerOptions; -import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.CreateInstanceRequest; +import com.google.spanner.admin.instance.v1.Instance; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.ProjectName; class CreateInstanceWithProcessingUnitsExample { @@ -38,39 +36,45 @@ static void createInstance() { } static void createInstance(String projectId, String instanceId) { - Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); - InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { - // Set Instance configuration. - String configId = "regional-us-central1"; - // This will create an instance with the processing power of 0.2 nodes. - int processingUnits = 500; - String displayName = "Descriptive name"; + // Set Instance configuration. + String configId = "regional-us-central1"; + // This will create an instance with the processing power of 0.2 nodes. + int processingUnits = 500; + String displayName = "Descriptive name"; - try { - // Creates a new instance - System.out.printf("Creating instance %s.%n", instanceId); - OperationFuture operation = - instanceAdminClient.createInstance(InstanceInfo - .newBuilder(InstanceId.of(projectId, instanceId)) - .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) - .setProcessingUnits(processingUnits) - .setDisplayName(displayName) - .build()); + try { + // Creates a new instance + System.out.printf("Creating instance %s.%n", instanceId); + Instance instance = + Instance.newBuilder() + .setDisplayName(displayName) + .setProcessingUnits(processingUnits) + .setConfig( + InstanceConfigName.of(projectId, configId).toString()) + .build(); + // Wait for the createInstance operation to finish. + System.out.printf("Waiting for operation on %s to complete...%n", instanceId); + Instance createdInstance = instanceAdminClient.createInstanceAsync( + CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of(projectId).toString()) + .setInstanceId(instanceId) + .setInstance(instance) + .build()).get(); - // Wait for the createInstance operation to finish. - System.out.printf("Waiting for operation on %s to complete...%n", instanceId); - Instance createdInstance = operation.get(); - - System.out.printf("Created instance %s.%n", createdInstance.getId().getInstance()); - - Instance instance = instanceAdminClient.getInstance(instanceId); - System.out.printf("Instance %s has %d processing units.%n", instance.getId().getInstance(), - instance.getProcessingUnits()); - } catch (Exception e) { - System.out.printf("Error: %s.%n", e.getMessage()); + System.out.printf("Created instance %s.%n", createdInstance.getName()); + System.out.printf("Instance %s has %d processing units.%n", createdInstance.getName(), + createdInstance.getProcessingUnits()); + } catch (Exception e) { + System.out.printf("Error: %s.%n", e.getMessage()); + } } - spanner.close(); } } //[END spanner_create_instance_with_processing_units] diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/CreateSequenceSample.java index 964b245ed8..757921080d 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateSequenceSample.java @@ -17,7 +17,7 @@ package com.example.spanner; // [START spanner_create_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,13 +25,16 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class CreateSequenceSample { + static void createSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -41,20 +44,18 @@ static void createSequence() { } static void createSequence(String projectId, String instanceId, String databaseId) { - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient + .updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), ImmutableList.of( "CREATE SEQUENCE Seq OPTIONS (sequence_kind = 'bit_reversed_positive')", "CREATE TABLE Customers (CustomerId INT64 DEFAULT " + "(GET_NEXT_SEQUENCE_VALUE(SEQUENCE Seq)), CustomerName STRING(1024)) " - + "PRIMARY KEY (CustomerId)"), - null) + + "PRIMARY KEY (CustomerId)")) .get(5, TimeUnit.MINUTES); System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSample.java index dda09591ee..c9484916a4 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSample.java @@ -17,10 +17,12 @@ package com.example.spanner; // [START spanner_create_table_with_foreign_key_delete_cascade] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; class CreateTableWithForeignKeyDeleteCascadeSample { @@ -30,37 +32,35 @@ static void createForeignKeyDeleteCascadeConstraint() { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - createForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); - } + createForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); } static void createForeignKeyDeleteCascadeConstraint( - DatabaseAdminClient adminClient, String instanceId, String databaseId) { - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of( - "CREATE TABLE Customers (\n" - + " CustomerId INT64 NOT NULL,\n" - + " CustomerName STRING(62) NOT NULL,\n" - + " ) PRIMARY KEY (CustomerId)", - "CREATE TABLE ShoppingCarts (\n" - + " CartId INT64 NOT NULL,\n" - + " CustomerId INT64 NOT NULL,\n" - + " CustomerName STRING(62) NOT NULL,\n" - + " CONSTRAINT FKShoppingCartsCustomerId FOREIGN KEY (CustomerId)\n" - + " REFERENCES Customers (CustomerId) ON DELETE CASCADE\n" - + " ) PRIMARY KEY (CartId)\n"), - null); + String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of( + "CREATE TABLE Customers (\n" + + " CustomerId INT64 NOT NULL,\n" + + " CustomerName STRING(62) NOT NULL,\n" + + " ) PRIMARY KEY (CustomerId)", + "CREATE TABLE ShoppingCarts (\n" + + " CartId INT64 NOT NULL,\n" + + " CustomerId INT64 NOT NULL,\n" + + " CustomerName STRING(62) NOT NULL,\n" + + " CONSTRAINT FKShoppingCartsCustomerId FOREIGN KEY (CustomerId)\n" + + " REFERENCES Customers (CustomerId) ON DELETE CASCADE\n" + + " ) PRIMARY KEY (CartId)\n")); - System.out.printf( - String.format( - "Created Customers and ShoppingCarts table with FKShoppingCartsCustomerId\n" - + "foreign key constraint on database %s on instance %s\n", - databaseId, instanceId)); + System.out.printf( + String.format( + "Created Customers and ShoppingCarts table with FKShoppingCartsCustomerId\n" + + "foreign key constraint on database %s on instance %s\n", + databaseId, instanceId)); + } } } // [END spanner_create_table_with_foreign_key_delete_cascade] diff --git a/samples/snippets/src/main/java/com/example/spanner/DeleteInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/DeleteInstanceConfigSample.java index de76673ca7..c2da7b3000 100644 --- a/samples/snippets/src/main/java/com/example/spanner/DeleteInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/DeleteInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * 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. @@ -17,12 +17,16 @@ package com.example.spanner; // [START spanner_delete_instance_config] -import com.google.cloud.spanner.InstanceAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.DeleteInstanceConfigRequest; +import com.google.spanner.admin.instance.v1.InstanceConfigName; class DeleteInstanceConfigSample { + static void deleteInstanceConfig() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; @@ -32,16 +36,24 @@ static void deleteInstanceConfig() { static void deleteInstanceConfig(String projectId, String instanceConfigId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, + instanceConfigId); + final DeleteInstanceConfigRequest request = + DeleteInstanceConfigRequest.newBuilder().setName(instanceConfigName.toString()).build(); + try { - System.out.printf("Deleting %s...\n", instanceConfigId); - instanceAdminClient.deleteInstanceConfig(instanceConfigId); - System.out.printf("Deleted instance configuration %s\n", instanceConfigId); + System.out.printf("Deleting %s...\n", instanceConfigName); + instanceAdminClient.deleteInstanceConfig(request); + System.out.printf("Deleted instance configuration %s\n", instanceConfigName); } catch (SpannerException e) { System.out.printf( "Error: Deleting instance configuration %s failed with error message: %s\n", - instanceConfigId, e.getMessage()); + instanceConfigName, e.getMessage()); } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSample.java index 13f39d129f..7c35b9f621 100644 --- a/samples/snippets/src/main/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSample.java @@ -17,10 +17,12 @@ package com.example.spanner; // [START spanner_drop_foreign_key_constraint_delete_cascade] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; class DropForeignKeyConstraintDeleteCascadeSample { @@ -30,28 +32,26 @@ static void deleteForeignKeyDeleteCascadeConstraint() { String instanceId = "my-instance"; String databaseId = "my-database"; - try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - deleteForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); - } + deleteForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); } static void deleteForeignKeyDeleteCascadeConstraint( - DatabaseAdminClient adminClient, String instanceId, String databaseId) { - adminClient.updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of( - "ALTER TABLE ShoppingCarts\n" - + " DROP CONSTRAINT FKShoppingCartsCustomerName\n"), - null); - - System.out.printf( - String.format( - "Altered ShoppingCarts table to drop FKShoppingCartsCustomerName\n" - + "foreign key constraint on database %s on instance %s\n", - databaseId, instanceId)); + String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of( + "ALTER TABLE ShoppingCarts\n" + + " DROP CONSTRAINT FKShoppingCartsCustomerName\n")); + + System.out.printf( + String.format( + "Altered ShoppingCarts table to drop FKShoppingCartsCustomerName\n" + + "foreign key constraint on database %s on instance %s\n", + databaseId, instanceId)); + } } } // [END spanner_drop_foreign_key_constraint_delete_cascade] diff --git a/samples/snippets/src/main/java/com/example/spanner/DropSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/DropSequenceSample.java index 6d054eea4d..9f1b32caed 100644 --- a/samples/snippets/src/main/java/com/example/spanner/DropSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/DropSequenceSample.java @@ -17,16 +17,19 @@ package com.example.spanner; // [START spanner_drop_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; 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.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class DropSequenceSample { + static void dropSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -37,19 +40,14 @@ static void dropSequence() { static void dropSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient + .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId), ImmutableList.of( "ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT", - "DROP SEQUENCE Seq"), - null) + "DROP SEQUENCE Seq")) .get(5, TimeUnit.MINUTES); - System.out.println( "Altered Customers table to drop DEFAULT from CustomerId column " + "and dropped the Seq sequence"); diff --git a/samples/snippets/src/main/java/com/example/spanner/EnableFineGrainedAccess.java b/samples/snippets/src/main/java/com/example/spanner/EnableFineGrainedAccess.java index c4c17645b6..e4e35bd95a 100644 --- a/samples/snippets/src/main/java/com/example/spanner/EnableFineGrainedAccess.java +++ b/samples/snippets/src/main/java/com/example/spanner/EnableFineGrainedAccess.java @@ -17,13 +17,18 @@ package com.example.spanner; // [START spanner_enable_fine_grained_access] -import com.google.cloud.Binding; -import com.google.cloud.Condition; -import com.google.cloud.Policy; -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.iam.v1.Binding; +import com.google.iam.v1.GetIamPolicyRequest; +import com.google.iam.v1.GetPolicyOptions; +import com.google.iam.v1.Policy; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.type.Expr; public class EnableFineGrainedAccess { @@ -46,12 +51,15 @@ static void enableFineGrainedAccess( String title, String role) { try (Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - Policy policy = adminClient.getDatabaseIAMPolicy(instanceId, databaseId, 3); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + final GetPolicyOptions options = + GetPolicyOptions.newBuilder().setRequestedPolicyVersion(3).build(); + final GetIamPolicyRequest getRequest = + GetIamPolicyRequest.newBuilder() + .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString()) + .setOptions(options).build(); + final Policy policy = databaseAdminClient.getIamPolicy(getRequest); int policyVersion = policy.getVersion(); // The policy in the response from getDatabaseIAMPolicy might use the policy version // that you specified, or it might use a lower policy version. For example, if you @@ -65,20 +73,17 @@ static void enableFineGrainedAccess( Binding binding1 = Binding.newBuilder() .setRole("roles/spanner.fineGrainedAccessUser") - .setMembers(ImmutableList.of(iamMember)) + .addAllMembers(ImmutableList.of(iamMember)) .build(); Binding binding2 = Binding.newBuilder() .setRole("roles/spanner.databaseRoleUser") .setCondition( - Condition.newBuilder() - .setDescription(title) - .setExpression( - String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role)) - .setTitle(title) - .build()) - .setMembers(ImmutableList.of(iamMember)) + Expr.newBuilder().setDescription(title).setExpression( + String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role) + ).setTitle(title).build()) + .addAllMembers(ImmutableList.of(iamMember)) .build(); ImmutableList bindings = ImmutableList.builder() @@ -90,10 +95,13 @@ static void enableFineGrainedAccess( Policy.newBuilder() .setVersion(policyVersion) .setEtag(policy.getEtag()) - .setBindings(bindings) + .addAllBindings(bindings) .build(); - Policy response = - adminClient.setDatabaseIAMPolicy(instanceId, databaseId, policyWithConditions); + final SetIamPolicyRequest setRequest = + SetIamPolicyRequest.newBuilder() + .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString()) + .setPolicy(policyWithConditions).build(); + final Policy response = databaseAdminClient.setIamPolicy(setRequest); System.out.printf( "Enabled fine-grained access in IAM with version %d%n", response.getVersion()); } diff --git a/samples/snippets/src/main/java/com/example/spanner/GetDatabaseDdlSample.java b/samples/snippets/src/main/java/com/example/spanner/GetDatabaseDdlSample.java index 93d32a5ec6..b84f1c0ccc 100644 --- a/samples/snippets/src/main/java/com/example/spanner/GetDatabaseDdlSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/GetDatabaseDdlSample.java @@ -18,10 +18,11 @@ //[START spanner_get_database_ddl] -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; -import java.util.List; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.GetDatabaseDdlResponse; public class GetDatabaseDdlSample { @@ -35,15 +36,13 @@ static void getDatabaseDdl() { static void getDatabaseDdl( String projectId, String instanceId, String databaseId) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); - final List ddls = databaseAdminClient.getDatabaseDdl(instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + final GetDatabaseDdlResponse response = + databaseAdminClient.getDatabaseDdl(DatabaseName.of(projectId, instanceId, databaseId)); System.out.println("Retrieved database DDL for " + databaseId); - for (String ddl : ddls) { + for (String ddl : response.getStatementsList()) { System.out.println(ddl); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java index 46f38b84d1..9dd8690f75 100644 --- a/samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * 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. @@ -18,35 +18,38 @@ //[START spanner_get_instance_config] -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceConfig; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceConfigName; public class GetInstanceConfigSample { static void getInstanceConfig() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; - final String instanceConfigName = "nam6"; - getInstanceConfig(projectId, instanceConfigName); + final String instanceConfigId = "nam6"; + getInstanceConfig(projectId, instanceConfigId); } - static void getInstanceConfig(String projectId, String instanceConfigName) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + static void getInstanceConfig(String projectId, String instanceConfigId) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, + instanceConfigId); - final InstanceConfig instanceConfig = instanceAdminClient - .getInstanceConfig(instanceConfigName); + final InstanceConfig instanceConfig = + instanceAdminClient.getInstanceConfig(instanceConfigName.toString()); System.out.printf( "Available leader options for instance config %s: %s%n", - instanceConfig.getId(), - instanceConfig.getLeaderOptions() + instanceConfig.getName(), + instanceConfig.getLeaderOptionsList() ); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/ListDatabaseRoles.java b/samples/snippets/src/main/java/com/example/spanner/ListDatabaseRoles.java index 9292405054..e16a55cb7b 100644 --- a/samples/snippets/src/main/java/com/example/spanner/ListDatabaseRoles.java +++ b/samples/snippets/src/main/java/com/example/spanner/ListDatabaseRoles.java @@ -17,16 +17,18 @@ package com.example.spanner; // [START spanner_list_database_roles] -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.DatabaseRole; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; -import java.util.concurrent.ExecutionException; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPage; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPagedResponse; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.DatabaseRole; public class ListDatabaseRoles { - static void listDatabaseRoles() throws InterruptedException, ExecutionException { + static void listDatabaseRoles() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; @@ -36,15 +38,16 @@ static void listDatabaseRoles() throws InterruptedException, ExecutionException static void listDatabaseRoles(String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); - String databasePath = DatabaseId.of(projectId, instanceId, databaseId).getName(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + DatabaseName databaseName = DatabaseName.of(projectId, instanceId, databaseId); + ListDatabaseRolesPagedResponse response + = databaseAdminClient.listDatabaseRoles(databaseName); System.out.println("List of Database roles"); - for (DatabaseRole role : adminClient.listDatabaseRoles(instanceId, databaseId).iterateAll()) { - System.out.printf("%s%n", role.getName()); + for (ListDatabaseRolesPage page : response.iteratePages()) { + for (DatabaseRole role : page.iterateAll()) { + System.out.printf("Obtained role %s%n", role.getName()); + } } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java b/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java index 6e6a246a30..631b72dc11 100644 --- a/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java @@ -18,11 +18,13 @@ //[START spanner_list_databases] -import com.google.api.gax.paging.Page; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabasesPage; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabasesPagedResponse; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.InstanceName; public class ListDatabasesSample { @@ -34,21 +36,20 @@ static void listDatabases() { } static void listDatabases(String projectId, String instanceId) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); - Page page = databaseAdminClient.listDatabases(instanceId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + ListDatabasesPagedResponse response = + databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId)); + System.out.println("Databases for projects/" + projectId + "/instances/" + instanceId); - while (page != null) { + + for (ListDatabasesPage page : response.iteratePages()) { for (Database database : page.iterateAll()) { final String defaultLeader = database.getDefaultLeader().equals("") ? "" : "(default leader = " + database.getDefaultLeader() + ")"; - System.out.println("\t" + database.getId() + " " + defaultLeader); + System.out.println("\t" + database.getName() + " " + defaultLeader); } - page = page.getNextPage(); } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java b/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java index bcf68b7d58..b42c52126b 100644 --- a/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * 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. @@ -17,15 +17,18 @@ package com.example.spanner; // [START spanner_list_instance_config_operations] -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.Options; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; import com.google.longrunning.Operation; import com.google.protobuf.InvalidProtocolBufferException; import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; +import com.google.spanner.admin.instance.v1.ListInstanceConfigOperationsRequest; +import com.google.spanner.admin.instance.v1.ProjectName; public class ListInstanceConfigOperationsSample { + static void listInstanceConfigOperations() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; @@ -34,32 +37,36 @@ static void listInstanceConfigOperations() { static void listInstanceConfigOperations(String projectId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - - try { - System.out.printf( - "Getting list of instance config operations for project %s...\n", - projectId); - final Iterable instanceConfigOperations = - instanceAdminClient - .listInstanceConfigOperations( - Options.filter( - "(metadata.@type=type.googleapis.com/" - + "google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)")) - .iterateAll(); - for (Operation operation : instanceConfigOperations) { - CreateInstanceConfigMetadata metadata = - operation.getMetadata().unpack(CreateInstanceConfigMetadata.class); - System.out.printf( - "Create instance config operation for %s is %d%% completed.\n", - metadata.getInstanceConfig().getName(), metadata.getProgress().getProgressPercent()); - } - } catch (InvalidProtocolBufferException e) { + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final ProjectName projectName = ProjectName.of(projectId); + System.out.printf( + "Getting list of instance config operations for project %s...\n", + projectId); + final ListInstanceConfigOperationsRequest request = + ListInstanceConfigOperationsRequest.newBuilder() + .setParent(projectName.toString()) + .setFilter("(metadata.@type=type.googleapis.com/" + + "google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)").build(); + final Iterable instanceConfigOperations = + instanceAdminClient.listInstanceConfigOperations(request).iterateAll(); + for (Operation operation : instanceConfigOperations) { + CreateInstanceConfigMetadata metadata = + operation.getMetadata().unpack(CreateInstanceConfigMetadata.class); System.out.printf( - "Error: Listing instance config operations failed with error message %s\n", - e.getMessage()); + "Create instance config operation for %s is %d%% completed.\n", + metadata.getInstanceConfig().getName(), metadata.getProgress().getProgressPercent()); } + System.out.printf( + "Obtained list of instance config operations for project %s...\n", + projectName); + } catch (InvalidProtocolBufferException e) { + System.out.printf( + "Error: Listing instance config operations failed with error message %s\n", + e.getMessage()); } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java b/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java index d0de12dfce..7c5391638b 100644 --- a/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * 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. @@ -18,32 +18,34 @@ //[START spanner_list_instance_configs] -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceConfig; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.ProjectName; public class ListInstanceConfigsSample { static void listInstanceConfigs() { // TODO(developer): Replace these variables before running the sample. - final String projectId = "my-project"; + String projectId = "my-project"; listInstanceConfigs(projectId); } static void listInstanceConfigs(String projectId) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - - for (InstanceConfig instanceConfig : instanceAdminClient.listInstanceConfigs().iterateAll()) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final ProjectName projectName = ProjectName.of(projectId); + for (InstanceConfig instanceConfig : + instanceAdminClient.listInstanceConfigs(projectName).iterateAll()) { System.out.printf( "Available leader options for instance config %s: %s%n", - instanceConfig.getId(), - instanceConfig.getLeaderOptions() + instanceConfig.getName(), + instanceConfig.getLeaderOptionsList() ); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java index e10c29807f..a3e4a9a677 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java @@ -17,7 +17,7 @@ package com.example.spanner; // [START spanner_postgresql_alter_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,13 +25,16 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgAlterSequenceSample { + static void pgAlterSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -42,14 +45,13 @@ static void pgAlterSequence() { static void pgAlterSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, - ImmutableList.of("ALTER SEQUENCE Seq SKIP RANGE 1000 5000000"), - null) + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + + databaseAdminClient + .updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + ImmutableList.of("ALTER SEQUENCE Seq SKIP RANGE 1000 5000000")) .get(5, TimeUnit.MINUTES); System.out.println( "Altered Seq sequence to skip an inclusive range between 1000 and 5000000"); diff --git a/samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java b/samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java index d76b26b178..abebdef39a 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java @@ -18,8 +18,6 @@ // [START spanner_postgresql_identifier_case_sensitivity] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.Mutation; @@ -28,7 +26,9 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.common.collect.Lists; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Collections; import java.util.concurrent.ExecutionException; @@ -43,34 +43,28 @@ static void pgCaseSensitivity() { } static void pgCaseSensitivity(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { // Spanner PostgreSQL follows the case sensitivity rules of PostgreSQL. This means that: // 1. Identifiers that are not double-quoted are folded to lower case. // 2. Identifiers that are double-quoted retain their case and are case-sensitive. // See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS // for more information. - final OperationFuture updateOperation = - databaseAdminClient.updateDatabaseDdl( - instanceId, - databaseId, - Collections.singleton( - "CREATE TABLE Singers (" - // SingerId will be folded to `singerid`. - + " SingerId bigint NOT NULL PRIMARY KEY," - // FirstName and LastName are double-quoted and will therefore retain their - // mixed case and are case-sensitive. This means that any statement that - // references any of these columns must use double quotes. - + " \"FirstName\" varchar(1024) NOT NULL," - + " \"LastName\" varchar(1024) NOT NULL" - + ")"), - null); - updateOperation.get(); + databaseAdminClient.updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), + Lists.newArrayList( + "CREATE TABLE Singers (" + // SingerId will be folded to `singerid`. + + " SingerId bigint NOT NULL PRIMARY KEY," + // FirstName and LastName are double-quoted and will therefore retain their + // mixed case and are case-sensitive. This means that any statement that + // references any of these columns must use double quotes. + + " \"FirstName\" varchar(1024) NOT NULL," + + " \"LastName\" varchar(1024) NOT NULL" + + ")")).get(); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); diff --git a/samples/snippets/src/main/java/com/example/spanner/PgCreateSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/PgCreateSequenceSample.java index e6e23f49b8..79445aa272 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgCreateSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgCreateSequenceSample.java @@ -17,7 +17,7 @@ package com.example.spanner; // [START spanner_postgresql_create_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,13 +25,16 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgCreateSequenceSample { + static void pgCreateSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -42,22 +45,18 @@ static void pgCreateSequence() { static void pgCreateSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient + .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId).toString(), ImmutableList.of( "CREATE SEQUENCE Seq BIT_REVERSED_POSITIVE;", "CREATE TABLE Customers (CustomerId BIGINT DEFAULT nextval('Seq'), " - + "CustomerName character varying(1024), PRIMARY KEY (CustomerId))"), - null) + + "CustomerName character varying(1024), PRIMARY KEY (CustomerId))")) .get(5, TimeUnit.MINUTES); System.out.println( - "Created Seq sequence and Customers table, where its key column " + "Created Seq sequence and Customers table, where the key column " + "CustomerId uses the sequence as a default value"); final DatabaseClient dbClient = diff --git a/samples/snippets/src/main/java/com/example/spanner/PgDropSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/PgDropSequenceSample.java index c0990c1c4d..129009e9b2 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgDropSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgDropSequenceSample.java @@ -17,16 +17,19 @@ package com.example.spanner; // [START spanner_postgresql_drop_sequence] -import com.google.cloud.spanner.DatabaseAdminClient; + import com.google.cloud.spanner.Spanner; 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.collect.ImmutableList; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgDropSequenceSample { + static void pgDropSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; @@ -36,17 +39,16 @@ static void pgDropSequence() { } static void pgDropSequence(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - dbAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient + .updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), ImmutableList.of( "ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT", - "DROP SEQUENCE Seq"), - null) + "DROP SEQUENCE Seq")) .get(5, TimeUnit.MINUTES); System.out.println( "Altered Customers table to drop DEFAULT from " diff --git a/samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java b/samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java index d79ba7b58f..30ee48ed6d 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java @@ -18,12 +18,11 @@ // [START spanner_postgresql_interleaved_table] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Arrays; import java.util.concurrent.ExecutionException; @@ -38,35 +37,29 @@ static void pgInterleavedTable() { } static void pgInterleavedTable(String projectId, String instanceId, String databaseId) { - try (Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { // The Spanner PostgreSQL dialect extends the PostgreSQL dialect with certain Spanner // specific features, such as interleaved tables. // See https://cloud.google.com/spanner/docs/postgresql/data-definition-language#create_table // for the full CREATE TABLE syntax. - final OperationFuture updateOperation = - databaseAdminClient.updateDatabaseDdl( + databaseAdminClient.updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, - databaseId, - Arrays.asList( - "CREATE TABLE Singers (" - + " SingerId bigint NOT NULL PRIMARY KEY," - + " FirstName varchar(1024) NOT NULL," - + " LastName varchar(1024) NOT NULL" - + ")", - "CREATE TABLE Albums (" - + " SingerId bigint NOT NULL," - + " AlbumId bigint NOT NULL," - + " Title varchar(1024) NOT NULL," - + " PRIMARY KEY (SingerId, AlbumId)" - + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE"), - null); - updateOperation.get(); + databaseId), + Arrays.asList( + "CREATE TABLE Singers (" + + " SingerId bigint NOT NULL PRIMARY KEY," + + " FirstName varchar(1024) NOT NULL," + + " LastName varchar(1024) NOT NULL" + + ")", + "CREATE TABLE Albums (" + + " SingerId bigint NOT NULL," + + " AlbumId bigint NOT NULL," + + " Title varchar(1024) NOT NULL," + + " PRIMARY KEY (SingerId, AlbumId)" + + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get(); System.out.println("Created interleaved table hierarchy using PostgreSQL dialect"); } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. diff --git a/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java index 44b96d4a40..600206c148 100644 --- a/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java @@ -16,24 +16,16 @@ package com.example.spanner; -import com.google.api.gax.longrunning.OperationFuture; 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.Database; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Dialect; -import com.google.cloud.spanner.Instance; -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceId; 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; @@ -45,19 +37,27 @@ 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.CreateDatabaseMetadata; +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.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.instance.v1.InstanceName; import com.google.spanner.v1.ExecuteSqlRequest; import java.math.BigDecimal; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -66,6 +66,7 @@ * Example code for using the Cloud Spanner PostgreSQL interface. */ public class PgSpannerSample { + // [START spanner_postgresql_insert_data] static final List SINGERS = Arrays.asList( @@ -83,7 +84,9 @@ public class PgSpannerSample { new Album(2, 3, "Terrified")); // [END spanner_postgresql_insert_data] - /** Class to contain performance sample data. */ + /** + * Class to contain performance sample data. + */ static class Performance { final long singerId; @@ -159,7 +162,9 @@ static class Performance { new BigDecimal("390650.99"))); // [END spanner_postgresql_insert_datatypes_data] - /** Class to contain venue sample data. */ + /** + * Class to contain venue sample data. + */ static class Venue { final long venueId; @@ -195,15 +200,18 @@ static class Venue { } // [START spanner_postgresql_create_database] - static void createPostgreSqlDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = dbAdminClient.createDatabase( - dbAdminClient.newDatabaseBuilder(id).setDialect(Dialect.POSTGRESQL).build(), - Collections.emptyList()); + 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 = op.get(); - System.out.println("Created database [" + db.getId() + "]"); - createTableUsingDdl(dbAdminClient, id); + 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(); @@ -272,9 +280,9 @@ static void deleteExampleData(DatabaseClient dbClient) { // [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"))) { + 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), @@ -287,12 +295,12 @@ static void query(DatabaseClient dbClient) { // [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"))) { + 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), @@ -303,15 +311,12 @@ static void read(DatabaseClient dbClient) { // [END spanner_postgresql_read_data] // [START spanner_postgresql_add_column] - static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint"), - null); + static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -404,11 +409,11 @@ static void queryMarketingBudget(DatabaseClient dbClient) { // 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"))) { + 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", @@ -424,16 +429,12 @@ static void queryMarketingBudget(DatabaseClient dbClient) { // [END spanner_postgresql_query_data_with_new_column] // [START spanner_postgresql_create_index] - static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), - null); + static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -449,13 +450,13 @@ static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { // [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"))) { + 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)); } @@ -464,17 +465,14 @@ static void readUsingIndex(DatabaseClient dbClient) { // [END spanner_postgresql_read_data_with_index] // [START spanner_postgresql_create_storing_index] - static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList( - "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) " - + "INCLUDE (MarketingBudget)"), - null); + static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -493,13 +491,13 @@ static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { 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"))) { + dbClient + .singleUse() + .readUsingIndex( + "Albums", + "AlbumsByAlbumTitle2", + KeySet.all(), + Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) { while (resultSet.next()) { System.out.printf( "%d %s %s\n", @@ -526,8 +524,8 @@ static void readOnlyTransaction(DatabaseClient dbClient) { queryResultSet.getString(2)); } try (ResultSet readResultSet = - transaction.read( - "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) { + transaction.read( + "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) { while (readResultSet.next()) { System.out.printf( "%d %d %s\n", @@ -542,10 +540,10 @@ static void readOnlyTransaction(DatabaseClient dbClient) { // [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"))) { + 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", @@ -656,35 +654,31 @@ static void writeWithTransactionUsingDml(DatabaseClient dbClient) { // [START spanner_postgresql_create_table_using_ddl] // [START spanner_postgresql_create_database] - static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.updateDatabaseDdl( - id.getInstanceId().getInstance(), - id.getDatabase(), - 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"), - null); + static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); - System.out.println("Created Singers & Albums tables in database: [" + id + "]"); + 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 (SpannerException) e.getCause(); + 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. @@ -697,11 +691,11 @@ static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseId id // [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"))) { + 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", @@ -749,17 +743,14 @@ static void updateWithTimestamp(DatabaseClient dbClient) { // [END spanner_postgresql_update_data_with_timestamp_column] // [START spanner_postgresql_add_timestamp_column] - static void addLastUpdateTimestampColumn(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList( - "ALTER TABLE Albums ADD COLUMN LastUpdateTime spanner.commit_timestamp"), - null); + static void addLastUpdateTimestampColumn( + DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -778,14 +769,14 @@ static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) { // 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"))) { + 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", @@ -801,24 +792,20 @@ static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) { // [END spanner_postgresql_query_data_with_timestamp_column] // [START spanner_postgresql_create_table_with_timestamp_column] - static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.updateDatabaseDdl( - id.getInstanceId().getInstance(), - id.getDatabase(), - 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"), - null); + static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, + DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); - System.out.println("Created Performances table in database: [" + id + "]"); + 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(); @@ -855,13 +842,13 @@ static void queryPerformancesTable(DatabaseClient dbClient) { // 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"))) { + 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", @@ -994,27 +981,24 @@ static void updateUsingBatchDml(DatabaseClient dbClient) { // [END spanner_postgresql_dml_batch_update] // [START spanner_postgresql_create_table_with_datatypes] - static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.updateDatabaseDdl( - id.getInstanceId().getInstance(), - id.getDatabase(), - 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))"), - null); + static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient, + DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); - System.out.println("Created Venues table in database: [" + id + "]"); + 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(); @@ -1222,9 +1206,9 @@ static void clientWithQueryOptions(DatabaseId db) { Spanner spanner = options.getService(); DatabaseClient dbClient = spanner.getDatabaseClient(db); try (ResultSet resultSet = - dbClient - .singleUse() - .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) { + 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)); @@ -1236,19 +1220,19 @@ static void clientWithQueryOptions(DatabaseId db) { // [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())) { + 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)); @@ -1258,22 +1242,26 @@ static void queryWithQueryOptions(DatabaseClient dbClient) { // [END spanner_postgresql_query_with_query_options] // [START spanner_postgresql_list_backup_operations] - static void listBackupOperations(InstanceAdminClient instanceAdminClient, DatabaseId databaseId) { - Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); - // Get create backup operations for the sample database. - Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( - TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, - TimeUnit.HOURS), 0); + 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.database:%s) AND " - + "(metadata.@type:type.googleapis.com/" - + "google.spanner.admin.database.v1.CreateBackupMetadata) AND " - + "(metadata.progress.start_time > \"%s\")", - databaseId.getName(), last24Hours); - Page operations = instance - .listBackupOperations(Options.filter(filter)); - for (com.google.longrunning.Operation op : operations.iterateAll()) { + "(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( @@ -1287,23 +1275,55 @@ static void listBackupOperations(InstanceAdminClient instanceAdminClient, Databa 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( - InstanceAdminClient instanceAdminClient, - DatabaseAdminClient dbAdminClient, - InstanceId instanceId) { - Instance instance = instanceAdminClient.getInstance(instanceId.getInstance()); + DatabaseAdminClient dbAdminClient, String projectId, String instanceId) { // Get optimize restored database operations. - Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( - TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, - TimeUnit.HOURS), 0); + 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); - for (Operation op : instance.listDatabaseOperations(Options.filter(filter)).iterateAll()) { + 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); @@ -1322,12 +1342,15 @@ static void listDatabaseOperations( static void run( DatabaseClient dbClient, DatabaseAdminClient dbAdminClient, - InstanceAdminClient instanceAdminClient, String command, - DatabaseId database) { + DatabaseId database, + String backupId) { + DatabaseName databaseName = DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase()); switch (command) { - case "createdatabase": - createPostgreSqlDatabase(dbAdminClient, database); + case "createpgdatabase": + createPostgreSqlDatabase(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase()); break; case "write": writeExampleData(dbClient); @@ -1342,7 +1365,7 @@ static void run( read(dbClient); break; case "addmarketingbudget": - addMarketingBudget(dbAdminClient, database); + addMarketingBudget(dbAdminClient, databaseName); break; case "update": update(dbClient); @@ -1354,13 +1377,13 @@ static void run( queryMarketingBudget(dbClient); break; case "addindex": - addIndex(dbAdminClient, database); + addIndex(dbAdminClient, databaseName); break; case "readindex": readUsingIndex(dbClient); break; case "addstoringindex": - addStoringIndex(dbAdminClient, database); + addStoringIndex(dbAdminClient, databaseName); break; case "readstoringindex": readStoringIndex(dbClient); @@ -1381,13 +1404,13 @@ static void run( writeWithTransactionUsingDml(dbClient); break; case "createtableusingddl": - createTableUsingDdl(dbAdminClient, database); + createTableUsingDdl(dbAdminClient, databaseName); break; case "readstaledata": readStaleData(dbClient); break; case "addlastupdatetimestampcolumn": - addLastUpdateTimestampColumn(dbAdminClient, database); + addLastUpdateTimestampColumn(dbAdminClient, databaseName); break; case "updatewithtimestamp": updateWithTimestamp(dbClient); @@ -1396,7 +1419,7 @@ static void run( queryMarketingBudgetWithTimestamp(dbClient); break; case "createtablewithtimestamp": - createTableWithTimestamp(dbAdminClient, database); + createTableWithTimestamp(dbAdminClient, databaseName); break; case "writewithtimestamp": writeExampleDataWithTimestamp(dbClient); @@ -1426,7 +1449,7 @@ static void run( updateUsingBatchDml(dbClient); break; case "createtablewithdatatypes": - createTableWithDatatypes(dbAdminClient, database); + createTableWithDatatypes(dbAdminClient, databaseName); break; case "writedatatypesdata": writeDatatypesData(dbClient); @@ -1459,10 +1482,12 @@ static void run( queryWithQueryOptions(dbClient); break; case "listbackupoperations": - listBackupOperations(instanceAdminClient, database); + listBackupOperations(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase(), backupId); break; case "listdatabaseoperations": - listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId()); + listDatabaseOperations(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance()); break; default: printUsageAndExit(); @@ -1525,9 +1550,10 @@ public static void main(String[] args) { // [START spanner_init_client] SpannerOptions options = SpannerOptions.newBuilder().build(); Spanner spanner = options.getService(); + DatabaseAdminClient dbAdminClient = null; try { // [END spanner_init_client] - String command = args[0]; + 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. @@ -1539,23 +1565,35 @@ public static void main(String[] args) { + 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); - DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + dbAdminClient = spanner.createDatabaseAdminClient(); // [END spanner_init_client] // Use client here... - run(dbClient, dbAdminClient, instanceAdminClient, command, db); + 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. */ + /** + * Class to contain singer sample data. + */ static class Singer { final long singerId; @@ -1569,7 +1607,9 @@ static class Singer { } } - /** Class to contain album sample data. */ + /** + * Class to contain album sample data. + */ static class Album { final long singerId; diff --git a/samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java index fa87d98151..af101f96cc 100644 --- a/samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java +++ b/samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java @@ -18,17 +18,17 @@ // [START spanner_restore_backup_with_encryption_key] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.BackupId; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Restore; +import static com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; -import com.google.cloud.spanner.encryption.EncryptionConfigs; -import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; +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.InstanceName; +import com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig; +import com.google.spanner.admin.database.v1.RestoreDatabaseRequest; import java.util.concurrent.ExecutionException; public class RestoreBackupWithEncryptionKey { @@ -43,8 +43,8 @@ static void restoreBackupWithEncryptionKey() { "projects/" + projectId + "/locations//keyRings//cryptoKeys/"; try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient adminClient = spanner.createDatabaseAdminClient()) { restoreBackupWithEncryptionKey( adminClient, projectId, @@ -57,19 +57,18 @@ static void restoreBackupWithEncryptionKey() { static Void restoreBackupWithEncryptionKey(DatabaseAdminClient adminClient, String projectId, String instanceId, String backupId, String restoreId, String kmsKeyName) { - final Restore restore = adminClient - .newRestoreBuilder( - BackupId.of(projectId, instanceId, backupId), - DatabaseId.of(projectId, instanceId, restoreId)) - .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName)) - .build(); - final OperationFuture operation = adminClient - .restoreDatabase(restore); - + 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 = operation.get(); + database = adminClient.restoreDatabaseAsync(request).get(); + ; } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. throw SpannerExceptionFactory.asSpannerException(e.getCause()); @@ -81,9 +80,9 @@ static Void restoreBackupWithEncryptionKey(DatabaseAdminClient adminClient, System.out.printf( "Database %s restored to %s from backup %s using encryption key %s%n", - database.getRestoreInfo().getSourceDatabase(), - database.getId(), - database.getRestoreInfo().getBackup(), + database.getRestoreInfo().getBackupInfo().getSourceDatabase(), + database.getName(), + database.getRestoreInfo().getBackupInfo().getBackup(), database.getEncryptionConfig().getKmsKeyName() ); return null; diff --git a/samples/snippets/src/main/java/com/example/spanner/SpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/SpannerSample.java index 43c04aaa8d..d406225c28 100644 --- a/samples/snippets/src/main/java/com/example/spanner/SpannerSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/SpannerSample.java @@ -20,28 +20,19 @@ import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.longrunning.OperationSnapshot; -import com.google.api.gax.paging.Page; 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.Timestamp; -import com.google.cloud.spanner.Backup; -import com.google.cloud.spanner.BackupId; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Instance; -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceId; 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.RestoreInfo; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerBatchUpdateException; @@ -53,27 +44,41 @@ 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.CreateDatabaseMetadata; +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.UpdateDatabaseDdlMetadata; +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; -import org.threeten.bp.LocalDate; -import org.threeten.bp.LocalDateTime; -import org.threeten.bp.OffsetDateTime; -import org.threeten.bp.temporal.ChronoField; /** * Example code for using the Cloud Spanner API. This example demonstrates all the common operations @@ -93,7 +98,9 @@ */ public class SpannerSample { - /** Class to contain singer sample data. */ + /** + * Class to contain singer sample data. + */ static class Singer { final long singerId; @@ -107,7 +114,9 @@ static class Singer { } } - /** Class to contain album sample data. */ + /** + * Class to contain album sample data. + */ static class Album { final long singerId; @@ -121,7 +130,9 @@ static class Album { } } - /** Class to contain performance sample data. */ + /** + * Class to contain performance sample data. + */ static class Performance { final long singerId; @@ -137,7 +148,9 @@ static class Performance { } } - /** Class to contain venue sample data. */ + /** + * Class to contain venue sample data. + */ static class Venue { final long venueId; @@ -175,17 +188,6 @@ static class Venue { } } - /** Get a database id to restore a backup to from the sample database id. */ - static String createRestoredSampleDbId(DatabaseId database) { - int index = database.getDatabase().indexOf('-'); - String prefix = database.getDatabase().substring(0, index); - String restoredDbId = database.getDatabase().replace(prefix, "restored"); - if (restoredDbId.length() > 30) { - restoredDbId = restoredDbId.substring(0, 30); - } - return restoredDbId; - } - // [START spanner_insert_data] static final List SINGERS = Arrays.asList( @@ -272,12 +274,13 @@ static String createRestoredSampleDbId(DatabaseId database) { // [END spanner_insert_datatypes_data] // [START spanner_create_database] - static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.createDatabase( - id.getInstanceId().getInstance(), - id.getDatabase(), - Arrays.asList( + 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)," @@ -291,11 +294,12 @@ static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) { + " AlbumId INT64 NOT NULL," + " AlbumTitle STRING(MAX)" + ") PRIMARY KEY (SingerId, AlbumId)," - + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")); + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build(); try { // Initiate the request which returns an OperationFuture. - Database db = op.get(); - System.out.println("Created database [" + db.getId() + "]"); + 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(); @@ -308,25 +312,23 @@ static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) { // [END spanner_create_database] // [START spanner_create_table_with_timestamp_column] - static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.updateDatabaseDdl( - id.getInstanceId().getInstance(), - id.getDatabase(), - 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"), - null); + static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, + DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); - System.out.println("Created Performances table in database: [" + id + "]"); + 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(); @@ -446,16 +448,12 @@ static void read(DatabaseClient dbClient) { // [END spanner_read_data] // [START spanner_add_column] - static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"), - null); + static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -565,16 +563,12 @@ static void queryMarketingBudget(DatabaseClient dbClient) { // [END spanner_query_data_with_new_column] // [START spanner_create_index] - static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), - null); + static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -636,18 +630,14 @@ static void readUsingIndex(DatabaseClient dbClient) { // [END spanner_read_data_with_index] // [START spanner_create_storing_index] - static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList( - "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) " - + "STORING (MarketingBudget)"), - null); + static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -729,18 +719,14 @@ static void readStaleData(DatabaseClient dbClient) { // [END spanner_read_stale_data] // [START spanner_add_timestamp_column] - static void addCommitTimestamp(DatabaseAdminClient adminClient, DatabaseId dbId) { - OperationFuture op = - adminClient.updateDatabaseDdl( - dbId.getInstanceId().getInstance(), - dbId.getDatabase(), - Arrays.asList( - "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP " - + "OPTIONS (allow_commit_timestamp=true)"), - null); + static void addCommitTimestamp(DatabaseAdminClient adminClient, DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); + 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. @@ -1186,18 +1172,18 @@ static void writeWithTransactionUsingDml(DatabaseClient dbClient) { album2Budget -= transfer; Statement updateStatement = Statement.newBuilder( - "UPDATE Albums " - + "SET MarketingBudget = @AlbumBudget " - + "WHERE SingerId = 1 and AlbumId = 1") + "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") + "UPDATE Albums " + + "SET MarketingBudget = @AlbumBudget " + + "WHERE SingerId = 2 and AlbumId = 2") .bind("AlbumBudget") .to(album2Budget) .build(); @@ -1255,30 +1241,26 @@ static void updateUsingBatchDml(DatabaseClient dbClient) { // [END spanner_dml_batch_update] // [START spanner_create_table_with_datatypes] - static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient, DatabaseId id) { - OperationFuture op = - dbAdminClient.updateDatabaseDdl( - id.getInstanceId().getInstance(), - id.getDatabase(), - 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)"), - null); + static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient, + DatabaseName databaseName) { try { // Initiate the request which returns an OperationFuture. - op.get(); - System.out.println("Created Venues table in database: [" + id + "]"); + 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(); @@ -1570,74 +1552,76 @@ static void queryWithQueryOptions(DatabaseClient dbClient) { // [END spanner_query_with_query_options] // [START spanner_create_backup] - static void createBackup(DatabaseAdminClient dbAdminClient, DatabaseId databaseId, - BackupId backupId, Timestamp versionTime) { + 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.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); - Backup backup = - dbAdminClient - .newBackupBuilder(backupId) - .setDatabase(databaseId) - .setExpireTime(expireTime) - .setVersionTime(versionTime) - .build(); + 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 [" + backup.getId() + "]..."); - OperationFuture op = backup.create(); + System.out.println("Creating backup [" + backupId + "]..."); try { // Wait for the backup operation to complete. - backup = op.get(); - System.out.println("Created backup [" + backup.getId() + "]"); + backup = dbAdminClient.createBackupAsync( + InstanceName.of(projectId, instanceId), backup, backupId).get(); + System.out.println("Created backup [" + backup.getName() + "]"); } catch (ExecutionException e) { - throw (SpannerException) e.getCause(); + throw SpannerExceptionFactory.asSpannerException(e); } catch (InterruptedException e) { throw SpannerExceptionFactory.propagateInterrupt(e); } // Reload the metadata of the backup from the server. - backup = backup.reload(); + 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.getId().getName(), - backup.getSize(), - LocalDateTime.ofEpochSecond( - backup.getProto().getCreateTime().getSeconds(), - backup.getProto().getCreateTime().getNanos(), - OffsetDateTime.now().getOffset()), - LocalDateTime.ofEpochSecond( - backup.getProto().getVersionTime().getSeconds(), - backup.getProto().getVersionTime().getNanos(), - OffsetDateTime.now().getOffset()) - )); + 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, DatabaseId databaseId, BackupId backupId) { + DatabaseAdminClient dbAdminClient, String projectId, String instanceId, + String databaseId, String backupId) { // Set expire time to 14 days from now. - Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); - - // Create a backup instance. - Backup backup = - dbAdminClient - .newBackupBuilder(backupId) - .setDatabase(databaseId) - .setExpireTime(expireTime) - .build(); - // Start the creation of a backup. - System.out.println("Creating backup [" + backup.getId() + "]..."); - OperationFuture op = backup.create(); + 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 [" + backup.getId() + "]..."); - dbAdminClient.cancelOperation(op.getName()); + 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. @@ -1647,11 +1631,11 @@ static void cancelCreateBackup( } if (pollingFuture.get().getErrorCode() == null) { // Backup was created before it could be cancelled. Delete the backup. - backup.delete(); - System.out.println("Backup operation for [" + backup.getId() + 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 [" + backup.getId() + "] successfully cancelled"); + System.out.println("Backup operation for [" + backupId + "] successfully cancelled"); } } catch (ExecutionException e) { throw SpannerExceptionFactory.newSpannerException(e.getCause()); @@ -1663,20 +1647,22 @@ static void cancelCreateBackup( // [START spanner_list_backup_operations] static void listBackupOperations( - InstanceAdminClient instanceAdminClient, DatabaseId databaseId, BackupId backupId) { - Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); - // Get create backup operations for the sample database. - Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( - TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, - TimeUnit.HOURS), 0); + 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)", - databaseId.getName()); - Page createBackupOperations = instance.listBackupOperations( - Options.filter(filter)); + "(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 { @@ -1693,24 +1679,27 @@ static void listBackupOperations( } } // 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)", - backupId.getName()); - Page copyBackupOperations = instance.listBackupOperations(Options.filter(filter)); + 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); + 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())); + 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()); @@ -1721,18 +1710,23 @@ static void listBackupOperations( // [START spanner_list_database_operations] static void listDatabaseOperations( - InstanceAdminClient instanceAdminClient, - DatabaseAdminClient dbAdminClient, - InstanceId instanceId) { - Instance instance = instanceAdminClient.getInstance(instanceId.getInstance()); + DatabaseAdminClient dbAdminClient, String projectId, String instanceId) { // Get optimize restored database operations. - Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( - TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, - TimeUnit.HOURS), 0); + 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); - for (Operation op : instance.listDatabaseOperations(Options.filter(filter)).iterateAll()) { + + "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); @@ -1750,74 +1744,92 @@ static void listDatabaseOperations( // [START spanner_list_backups] static void listBackups( - InstanceAdminClient instanceAdminClient, DatabaseId databaseId, BackupId backupId) { - Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); + 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 : instance.listBackups().iterateAll()) { + 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.getBackup())); - for (Backup backup : instance.listBackups( - Options.filter(String.format("name:%s", backupId.getBackup()))).iterateAll()) { + 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.getDatabase())); - for (Backup backup : instance.listBackups( - Options.filter(String.format("database:%s", databaseId.getDatabase()))).iterateAll()) { + "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. - Timestamp expireTime = 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.toString())); - for (Backup backup : - instance.listBackups( - Options.filter(String.format("expire_time < \"%s\"", expireTime.toString()))) - .iterateAll()) { + 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 : instance.listBackups(Options.filter("size_bytes > 100")).iterateAll()) { + 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. - Timestamp createTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( - System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS)); + 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())); - for (Backup backup : - instance - .listBackups(Options.filter( - String.format("create_time >= \"%s\" AND state:READY", createTime.toString()))) - .iterateAll()) { + 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:"); - Page page = instance.listBackups(Options.pageSize(10)); + listBackupsRequest = + ListBackupsRequest.newBuilder().setParent(instanceName.toString()).setPageSize(10).build(); while (true) { - for (Backup backup : page.getValues()) { + ListBackupsPagedResponse response = dbAdminClient.listBackups(listBackupsRequest); + for (Backup backup : response.getPage().iterateAll()) { System.out.println(backup); } - if (!page.hasNextPage()) { + String nextPageToken = response.getNextPageToken(); + if (!Strings.isNullOrEmpty(nextPageToken)) { + listBackupsRequest = listBackupsRequest.toBuilder().setPageToken(nextPageToken).build(); + } else { break; } - page = page.getNextPage(); } } // [END spanner_list_backups] @@ -1825,31 +1837,35 @@ static void listBackups( // [START spanner_restore_backup] static void restoreBackup( DatabaseAdminClient dbAdminClient, - BackupId backupId, - DatabaseId sourceDatabaseId, - DatabaseId restoreToDatabase) { - Backup backup = dbAdminClient.newBackupBuilder(backupId).build(); + 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.getId().toString(), - restoreToDatabase.toString())); + "Restoring backup [%s] to database [%s]...", backup.getName(), restoreToDatabaseId)); try { - OperationFuture op = backup.restore(restoreToDatabase); + 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. - Database db = op.get(); - // Refresh database metadata and get the restore info. - RestoreInfo restore = db.reload().getRestoreInfo(); - Timestamp versionTime = Timestamp.fromProto(restore - .getProto() - .getBackupInfo() - .getVersionTime()); + 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 [" - + restore.getSourceDatabase().getName() + + db.getName() + "] from [" - + restore.getBackup().getName() - + "] with version time [" + versionTime + "]"); + + restoreInfo.getBackupInfo().getBackup() + + "] with version time [" + backupInfo.getVersionTime() + "]"); } catch (ExecutionException e) { throw SpannerExceptionFactory.newSpannerException(e.getCause()); } catch (InterruptedException e) { @@ -1859,49 +1875,59 @@ static void restoreBackup( // [END spanner_restore_backup] // [START spanner_update_backup] - static void updateBackup(DatabaseAdminClient dbAdminClient, BackupId backupId) { + 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.newBackupBuilder(backupId).build().reload(); + 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 expireTime = - Timestamp.ofTimeMicroseconds( - TimeUnit.SECONDS.toMicros(backup.getExpireTime().getSeconds()) - + TimeUnit.NANOSECONDS.toMicros(backup.getExpireTime().getNanos()) + 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 - expireTime = expireTime.compareTo(backup.getMaxExpireTime()) - < 0 ? expireTime : backup.getMaxExpireTime(); - int timeDiff = expireTime.compareTo(backup.getExpireTime()); - Timestamp newExpireTime = (timeDiff < 0) ? expireTime : backup.getExpireTime(); + 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(), - LocalDateTime.ofEpochSecond( - expireTime.getSeconds(), - expireTime.getNanos(), - OffsetDateTime.now().getOffset()).toString())); + java.time.OffsetDateTime.ofInstant( + Instant.ofEpochSecond(newExpireTime.getSeconds(), + newExpireTime.getNanos()), ZoneId.systemDefault()))); // Update expire time. - backup = backup.toBuilder().setExpireTime(expireTime).build(); - backup.updateExpireTime(); + 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, BackupId backupId) { - Backup backup = dbAdminClient.newBackupBuilder(backupId).build(); + 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 + "]..."); - backup.delete(); + dbAdminClient.deleteBackup(backupName); // Verify that the backup is deleted. - if (backup.exists()) { - System.out.println("Delete backup [" + backupId + "] failed"); - throw new RuntimeException("Delete backup [" + backupId + "] failed"); - } else { - System.out.println("Deleted backup [" + backupId + "]"); + 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] @@ -1909,13 +1935,13 @@ static void deleteBackup(DatabaseAdminClient dbAdminClient, BackupId backupId) { static void run( DatabaseClient dbClient, DatabaseAdminClient dbAdminClient, - InstanceAdminClient instanceAdminClient, String command, DatabaseId database, - BackupId backup) { + String backupId) { switch (command) { case "createdatabase": - createDatabase(dbAdminClient, database); + createDatabase(dbAdminClient, InstanceName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance()), database.getDatabase()); break; case "write": writeExampleData(dbClient); @@ -1930,7 +1956,8 @@ static void run( read(dbClient); break; case "addmarketingbudget": - addMarketingBudget(dbAdminClient, database); + addMarketingBudget(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "update": update(dbClient); @@ -1942,7 +1969,8 @@ static void run( queryMarketingBudget(dbClient); break; case "addindex": - addIndex(dbAdminClient, database); + addIndex(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "readindex": readUsingIndex(dbClient); @@ -1951,7 +1979,8 @@ static void run( queryUsingIndex(dbClient); break; case "addstoringindex": - addStoringIndex(dbAdminClient, database); + addStoringIndex(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "readstoringindex": readStoringIndex(dbClient); @@ -1963,7 +1992,8 @@ static void run( readStaleData(dbClient); break; case "addcommittimestamp": - addCommitTimestamp(dbAdminClient, database); + addCommitTimestamp(dbAdminClient, DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "updatewithtimestamp": updateWithTimestamp(dbClient); @@ -1972,7 +2002,9 @@ static void run( queryMarketingBudgetWithTimestamp(dbClient); break; case "createtablewithtimestamp": - createTableWithTimestamp(dbAdminClient, database); + createTableWithTimestamp(dbAdminClient, + DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "writewithtimestamp": writeExampleDataWithTimestamp(dbClient); @@ -2035,7 +2067,9 @@ static void run( updateUsingBatchDml(dbClient); break; case "createtablewithdatatypes": - createTableWithDatatypes(dbAdminClient, database); + createTableWithDatatypes(dbAdminClient, + DatabaseName.of(database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase())); break; case "writedatatypesdata": writeDatatypesData(dbClient); @@ -2074,35 +2108,41 @@ static void run( queryWithQueryOptions(dbClient); break; case "createbackup": - createBackup(dbAdminClient, database, backup, getVersionTime(dbClient)); + createBackup(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase(), + backupId, getVersionTime(dbClient)); break; case "cancelcreatebackup": cancelCreateBackup( dbAdminClient, - database, - BackupId.of(backup.getInstanceId(), backup.getBackup() + "_cancel")); + database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase(), + backupId + "_cancel"); break; case "listbackupoperations": - listBackupOperations(instanceAdminClient, database, backup); + listBackupOperations(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase(), backupId); break; case "listdatabaseoperations": - listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId()); + listDatabaseOperations(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance()); break; case "listbackups": - listBackups(instanceAdminClient, database, backup); + listBackups(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), database.getDatabase(), backupId); break; case "restorebackup": restoreBackup( - dbAdminClient, - backup, - database, - DatabaseId.of(database.getInstanceId(), createRestoredSampleDbId(database))); + dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), backupId, database.getDatabase()); break; case "updatebackup": - updateBackup(dbAdminClient, backup); + updateBackup(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), backupId); break; case "deletebackup": - deleteBackup(dbAdminClient, backup); + deleteBackup(dbAdminClient, database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), backupId); break; default: printUsageAndExit(); @@ -2111,13 +2151,13 @@ static void run( static Timestamp getVersionTime(DatabaseClient dbClient) { // Generates a version time for the backup - Timestamp versionTime; + com.google.cloud.Timestamp versionTime; try (ResultSet resultSet = dbClient.singleUse() .executeQuery(Statement.of("SELECT CURRENT_TIMESTAMP()"))) { resultSet.next(); versionTime = resultSet.getTimestamp(0); } - return versionTime; + return versionTime.toProto(); } static void printUsageAndExit() { @@ -2185,15 +2225,16 @@ static void printUsageAndExit() { System.exit(1); } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { if (args.length != 3 && args.length != 4) { printUsageAndExit(); } // [START init_client] SpannerOptions options = SpannerOptions.newBuilder().build(); Spanner spanner = options.getService(); + DatabaseAdminClient dbAdminClient = null; try { - String command = args[0]; + 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. @@ -2206,25 +2247,26 @@ public static void main(String[] args) throws Exception { printUsageAndExit(); } // Generate a backup id for the sample database. - String backupName = - String.format( - "%s_%02d", - db.getDatabase(), LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR)); - BackupId backup = BackupId.of(db.getInstanceId(), backupName); + String backupId = null; if (args.length == 4) { - backupName = args[3]; + backupId = args[3]; } // [START init_client] DatabaseClient dbClient = spanner.getDatabaseClient(db); - DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + dbAdminClient = spanner.createDatabaseAdminClient(); + // Use client here... // [END init_client] - run(dbClient, dbAdminClient, instanceAdminClient, command, db, backup); + 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] diff --git a/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java b/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java index c87c27690a..57bb8e9159 100644 --- a/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java @@ -17,15 +17,18 @@ package com.example.spanner; // [START spanner_update_database] + import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.Spanner; 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.collect.Lists; +import com.google.protobuf.FieldMask; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -37,22 +40,31 @@ static void updateDatabase() { final String projectId = "my-project"; final String instanceId = "my-instance"; final String databaseId = "my-database"; + updateDatabase(projectId, instanceId, databaseId); } - static void updateDatabase(String projectId, String instanceId, String databaseId) { + static void updateDatabase( + String projectId, String instanceId, String databaseId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); - - DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); - Database databaseToUpdate = - databaseAdminClient.newDatabaseBuilder(dbId).enableDropProtection().build(); + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + final Database database = + Database.newBuilder() + .setName(DatabaseName.of(projectId, instanceId, databaseId).toString()) + .setEnableDropProtection(true).build(); + final UpdateDatabaseRequest updateDatabaseRequest = + UpdateDatabaseRequest.newBuilder() + .setDatabase(database) + .setUpdateMask( + FieldMask.newBuilder().addAllPaths( + Lists.newArrayList("enable_drop_protection")).build()) + .build(); OperationFuture operation = - databaseAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); - System.out.printf("Waiting for update operation for %s to complete...\n", dbId); + databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest); + System.out.printf("Waiting for update operation for %s to complete...\n", databaseId); Database updatedDb = operation.get(5, TimeUnit.MINUTES); - System.out.printf("Updated database %s.\n", updatedDb.getId().getName()); + System.out.printf("Updated database %s.\n", updatedDb.getName()); } catch (ExecutionException | TimeoutException e) { // If the operation failed during execution, expose the cause. throw SpannerExceptionFactory.asSpannerException(e.getCause()); diff --git a/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java b/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java index f3ce004a69..701240cb12 100644 --- a/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java @@ -18,13 +18,12 @@ //[START spanner_update_database_with_default_leader] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.DatabaseAdminClient; 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.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.spanner.admin.database.v1.DatabaseName; import java.util.Collections; import java.util.concurrent.ExecutionException; @@ -41,26 +40,20 @@ static void updateDatabaseWithDefaultLeader() { static void updateDatabaseWithDefaultLeader( String projectId, String instanceId, String databaseId, String defaultLeader) { - try (Spanner spanner = SpannerOptions - .newBuilder() - .setProjectId(projectId) - .build() - .getService()) { - final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); - final OperationFuture operation = databaseAdminClient - .updateDatabaseDdl( - instanceId, - databaseId, + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + databaseAdminClient + .updateDatabaseDdlAsync( + DatabaseName.of(projectId, instanceId, databaseId), Collections.singletonList( String.format( "ALTER DATABASE `%s` SET OPTIONS (default_leader = '%s')", databaseId, defaultLeader ) - ), - null - ); - operation.get(); + ) + ).get(); System.out.println("Updated default leader to " + defaultLeader); } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. diff --git a/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java index 6450f265e1..c10175abe1 100644 --- a/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * 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. @@ -17,21 +17,21 @@ package com.example.spanner; // [START spanner_update_instance_config] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.InstanceConfigId; -import com.google.cloud.spanner.InstanceConfigInfo; -import com.google.cloud.spanner.InstanceConfigInfo.InstanceConfigField; + import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata; +import com.google.protobuf.FieldMask; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.UpdateInstanceConfigRequest; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; class UpdateInstanceConfigSample { + static void updateInstanceConfig() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; @@ -41,27 +41,42 @@ static void updateInstanceConfig() { static void updateInstanceConfig(String projectId, String instanceConfigId) { try (Spanner spanner = - SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - InstanceConfigInfo instanceConfigInfo = - InstanceConfig.newBuilder(InstanceConfigId.of(projectId, instanceConfigId)) + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService(); + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + final InstanceConfigName instanceConfigName = + InstanceConfigName.of(projectId, instanceConfigId); + final InstanceConfig instanceConfig = + InstanceConfig.newBuilder() + .setName(instanceConfigName.toString()) .setDisplayName("updated custom instance config") - .addLabel("updated", "true") - .build(); - final OperationFuture operation = - instanceAdminClient.updateInstanceConfig( - instanceConfigInfo, - ImmutableList.of(InstanceConfigField.DISPLAY_NAME, InstanceConfigField.LABELS)); + .putLabels("updated", "true").build(); + /** + * The field mask must always be specified; this prevents any future + * fields in [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig] + * from being erased accidentally by clients that do not know about them. + */ + final UpdateInstanceConfigRequest updateInstanceConfigRequest = + UpdateInstanceConfigRequest.newBuilder() + .setInstanceConfig(instanceConfig) + .setUpdateMask( + FieldMask.newBuilder().addAllPaths(ImmutableList.of("display_name", "labels")) + .build()).build(); try { - System.out.printf("Waiting for update operation on %s to complete...\n", instanceConfigId); - InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES); + System.out.printf("Waiting for update operation on %s to complete...\n", + instanceConfigName); + InstanceConfig instanceConfigResult = + instanceAdminClient.updateInstanceConfigAsync( + updateInstanceConfigRequest).get(5, TimeUnit.MINUTES); System.out.printf( "Updated instance configuration %s with new display name %s\n", - instanceConfig.getId(), instanceConfig.getDisplayName()); + instanceConfigResult.getName(), instanceConfig.getDisplayName()); } catch (ExecutionException | TimeoutException e) { System.out.printf( "Error: Updating instance config %s failed with error message %s\n", - instanceConfigInfo.getId(), e.getMessage()); + instanceConfig.getName(), e.getMessage()); e.printStackTrace(); } catch (InterruptedException e) { System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/.gitkeep b/samples/snippets/src/main/java/com/example/spanner/admin/archived/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddAndDropDatabaseRole.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddAndDropDatabaseRole.java new file mode 100644 index 0000000000..cce8543492 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddAndDropDatabaseRole.java @@ -0,0 +1,88 @@ +/* + * 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.archived; + +// [START spanner_add_and_drop_database_role] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class AddAndDropDatabaseRole { + + static void addAndDropDatabaseRole() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + String parentRole = "my-new-parent-role"; + String childRole = "my-new-child-role"; + addAndDropDatabaseRole(projectId, instanceId, databaseId, parentRole, childRole); + } + + static void addAndDropDatabaseRole( + String projectId, String instanceId, String databaseId, String parentRole, String childRole) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + OperationFuture operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of( + "CREATE ROLE " + parentRole, + "GRANT SELECT ON TABLE Albums TO ROLE " + parentRole, + "CREATE ROLE " + childRole, + "GRANT ROLE " + parentRole + " TO ROLE " + childRole), + null); + try { + System.out.println("Waiting for role create operation to complete..."); + operation.get(5, TimeUnit.MINUTES); + System.out.printf( + "Created roles %s and %s and granted privileges%n", parentRole, childRole); + // Delete role and membership. + operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of( + "REVOKE ROLE " + parentRole + " FROM ROLE " + childRole, + "DROP ROLE " + childRole), + null); + System.out.println("Waiting for role revoke & drop operation to complete..."); + operation.get(5, TimeUnit.MINUTES); + System.out.printf("Revoked privileges and dropped role %s%n", childRole); + } catch (ExecutionException | TimeoutException e) { + System.out.printf( + "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage()); + e.printStackTrace(); + } catch (InterruptedException e) { + System.out.println( + "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted"); + } + } + } +} +// [END spanner_add_and_drop_database_role] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonColumnSample.java similarity index 55% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonColumnSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonColumnSample.java index 32d1daea2d..8be7d6bd58 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonColumnSample.java @@ -14,35 +14,43 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_add_json_column] -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.concurrent.ExecutionException; class AddJsonColumnSample { - static void addJsonColumn() throws InterruptedException, ExecutionException, IOException { + static void addJsonColumn() throws InterruptedException, ExecutionException { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - addJsonColumn(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + addJsonColumn(adminClient, instanceId, databaseId); + } } - static void addJsonColumn(String projectId, String instanceId, String databaseId) - throws InterruptedException, ExecutionException, IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void addJsonColumn(DatabaseAdminClient adminClient, String instanceId, String databaseId) + throws InterruptedException, ExecutionException { + OperationFuture operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSON"), + null); // Wait for the operation to finish. // This will throw an ExecutionException if the operation fails. - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSON")).get(); + operation.get(); System.out.printf("Successfully added column `VenueDetails`%n"); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonbColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonbColumnSample.java similarity index 55% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonbColumnSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonbColumnSample.java index 800c2d3d65..102ea36b0e 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddJsonbColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddJsonbColumnSample.java @@ -14,36 +14,43 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_jsonb_add_column] -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.concurrent.ExecutionException; class AddJsonbColumnSample { - static void addJsonbColumn() throws InterruptedException, ExecutionException, IOException { + static void addJsonbColumn() throws InterruptedException, ExecutionException { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - addJsonbColumn(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + addJsonbColumn(adminClient, instanceId, databaseId); + } } - static void addJsonbColumn(String projectId, String instanceId, String databaseId) - throws InterruptedException, ExecutionException, IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - // JSONB datatype is only supported with PostgreSQL-dialect databases. + static void addJsonbColumn(DatabaseAdminClient adminClient, String instanceId, String databaseId) + throws InterruptedException, ExecutionException { + OperationFuture operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"), + null); // Wait for the operation to finish. // This will throw an ExecutionException if the operation fails. - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB")).get(); + operation.get(); System.out.printf("Successfully added column `VenueDetails`%n"); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddNumericColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddNumericColumnSample.java similarity index 54% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/AddNumericColumnSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/AddNumericColumnSample.java index 191e0377b6..347aaf5a9e 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddNumericColumnSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AddNumericColumnSample.java @@ -14,36 +14,44 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_add_numeric_column] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.concurrent.ExecutionException; class AddNumericColumnSample { - static void addNumericColumn() throws InterruptedException, ExecutionException, IOException { + static void addNumericColumn() throws InterruptedException, ExecutionException { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - addNumericColumn(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + addNumericColumn(adminClient, instanceId, databaseId); + } } - static void addNumericColumn(String projectId, String instanceId, String databaseId) - throws InterruptedException, ExecutionException, IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void addNumericColumn( + DatabaseAdminClient adminClient, String instanceId, String databaseId) + throws InterruptedException, ExecutionException { + OperationFuture operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER TABLE Venues ADD COLUMN Revenue NUMERIC"), + null); // Wait for the operation to finish. // This will throw an ExecutionException if the operation fails. - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of("ALTER TABLE Venues ADD COLUMN Revenue NUMERIC")).get(); + operation.get(); System.out.printf("Successfully added column `Revenue`%n"); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterSequenceSample.java similarity index 87% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterSequenceSample.java index 05ae63a7a5..294bfdf6e2 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterSequenceSample.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_alter_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,18 +25,14 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class AlterSequenceSample { - - static void alterSequence() throws IOException { + static void alterSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -44,17 +40,19 @@ static void alterSequence() throws IOException { alterSequence(projectId, instanceId, databaseId); } - static void alterSequence(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); + static void alterSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - databaseAdminClient - .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId), + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "ALTER SEQUENCE Seq SET OPTIONS " - + "(skip_range_min = 1000, skip_range_max = 5000000)")) + + "(skip_range_min = 1000, skip_range_max = 5000000)"), + null) .get(5, TimeUnit.MINUTES); System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSample.java similarity index 68% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSample.java index 5784bfab0c..ebf8a3f053 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSample.java @@ -14,38 +14,41 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_alter_table_with_foreign_key_delete_cascade] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; class AlterTableWithForeignKeyDeleteCascadeSample { - static void alterForeignKeyDeleteCascadeConstraint() throws IOException { + static void alterForeignKeyDeleteCascadeConstraint() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - alterForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + alterForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); + } } static void alterForeignKeyDeleteCascadeConstraint( - String projectId, String instanceId, String databaseId) throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - databaseAdminClient.updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, - databaseId), + DatabaseAdminClient adminClient, String instanceId, String databaseId) { + adminClient.updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "ALTER TABLE ShoppingCarts\n" + " ADD CONSTRAINT FKShoppingCartsCustomerName\n" + " FOREIGN KEY (CustomerName)\n" + " REFERENCES Customers(CustomerName)\n" - + " ON DELETE CASCADE\n")); + + " ON DELETE CASCADE\n"), + null); System.out.printf( String.format( "Altered ShoppingCarts table with FKShoppingCartsCustomerName\n" diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CopyBackupSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CopyBackupSample.java new file mode 100644 index 0000000000..3b3c192a5a --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CopyBackupSample.java @@ -0,0 +1,101 @@ +/* + * 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.archived; + +// [START spanner_copy_backup] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.DatabaseAdminClient; +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.spanner.admin.database.v1.CopyBackupMetadata; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class CopyBackupSample { + static void copyBackup() { + // 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 (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + 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)); + // Creates a copy of an existing backup. + Backup destinationBackup = + databaseAdminClient + .newBackupBuilder(BackupId.of(projectId, instanceId, destinationBackupId)) + .setExpireTime(expireTime) + .build(); + + // Initiate the request which returns an OperationFuture. + System.out.println("Copying backup [" + destinationBackup.getId() + "]..."); + OperationFuture operation = + databaseAdminClient.copyBackup( + BackupId.of(projectId, instanceId, sourceBackupId), destinationBackup); + try { + // Wait for the backup operation to complete. + destinationBackup = operation.get(); + System.out.println("Copied backup [" + destinationBackup.getId() + "]"); + } 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 = destinationBackup.reload(); + System.out.println( + String.format( + "Backup %s of size %d bytes was copied at %s for version of database at %s", + destinationBackup.getId().getName(), + destinationBackup.getSize(), + LocalDateTime.ofEpochSecond( + destinationBackup.getProto().getCreateTime().getSeconds(), + destinationBackup.getProto().getCreateTime().getNanos(), + OffsetDateTime.now().getOffset()), + LocalDateTime.ofEpochSecond( + destinationBackup.getProto().getVersionTime().getSeconds(), + destinationBackup.getProto().getVersionTime().getNanos(), + OffsetDateTime.now().getOffset()))); + return; + } +} +// [END spanner_copy_backup] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateBackupWithEncryptionKey.java new file mode 100644 index 0000000000..23ddd69917 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateBackupWithEncryptionKey.java @@ -0,0 +1,107 @@ +/* + * 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.archived; + +// [START spanner_create_backup_with_encryption_key] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.encryption.EncryptionConfigs; +import com.google.spanner.admin.database.v1.CreateBackupMetadata; +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 InterruptedException { + // 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 (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + createBackupWithEncryptionKey( + adminClient, + projectId, + instanceId, + databaseId, + backupId, + kmsKeyName); + } + } + + static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient, + String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName) + throws InterruptedException { + // Set expire time to 14 days from now. + final Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( + System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); + final Backup backupToCreate = adminClient + .newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) + .setDatabase(DatabaseId.of(projectId, instanceId, databaseId)) + .setExpireTime(expireTime) + .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName)) + .build(); + final OperationFuture operation = adminClient + .createBackup(backupToCreate); + + Backup backup; + try { + System.out.println("Waiting for operation to complete..."); + backup = operation.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.getId().getName(), + backup.getSize(), + LocalDateTime.ofEpochSecond( + backup.getProto().getCreateTime().getSeconds(), + backup.getProto().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/archived/CreateDatabaseWithDefaultLeaderSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSample.java new file mode 100644 index 0000000000..8bfc6422cf --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSample.java @@ -0,0 +1,85 @@ +/* + * 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.archived; + +//[START spanner_create_database_with_default_leader] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +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.spanner.admin.database.v1.CreateDatabaseMetadata; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; + +public class CreateDatabaseWithDefaultLeaderSample { + + static void createDatabaseWithDefaultLeader() { + // TODO(developer): Replace these variables before running the sample. + final String projectId = "my-project"; + final String instanceId = "my-instance"; + final String databaseId = "my-database"; + final String defaultLeader = "my-default-leader"; + createDatabaseWithDefaultLeader(projectId, instanceId, databaseId, defaultLeader); + } + + static void createDatabaseWithDefaultLeader( + String projectId, String instanceId, String databaseId, String defaultLeader) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + final OperationFuture operation = databaseAdminClient + .createDatabase( + instanceId, + databaseId, + Arrays.asList( + "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", + "ALTER DATABASE " + "`" + databaseId + "`" + + " SET OPTIONS ( default_leader = '" + defaultLeader + "' )" + ) + ); + final Database database = operation.get(); + System.out.println("Created database [" + database.getId() + "]"); + System.out.println("\tDefault leader: " + database.getDefaultLeader()); + } 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_with_default_leader] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithEncryptionKey.java new file mode 100644 index 0000000000..2064423547 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithEncryptionKey.java @@ -0,0 +1,100 @@ +/* + * 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.archived; + +// [START spanner_create_database_with_encryption_key] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.encryption.EncryptionConfigs; +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class CreateDatabaseWithEncryptionKey { + + static void createDatabaseWithEncryptionKey() { + // 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 (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + createDatabaseWithEncryptionKey( + adminClient, + projectId, + instanceId, + databaseId, + kmsKeyName); + } + } + + static void createDatabaseWithEncryptionKey(DatabaseAdminClient adminClient, + String projectId, String instanceId, String databaseId, String kmsKeyName) { + final Database databaseToCreate = adminClient + .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) + .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName)) + .build(); + final OperationFuture operation = adminClient + .createDatabase(databaseToCreate, Arrays.asList( + "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" + )); + try { + System.out.println("Waiting for operation to complete..."); + Database createdDatabase = operation.get(120, TimeUnit.SECONDS); + + System.out.printf( + "Database %s created with encryption key %s%n", + createdDatabase.getId(), + 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/archived/CreateDatabaseWithVersionRetentionPeriodSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithVersionRetentionPeriodSample.java new file mode 100644 index 0000000000..1dea5af204 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateDatabaseWithVersionRetentionPeriodSample.java @@ -0,0 +1,86 @@ +/* + * 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.archived; + +// [START spanner_create_database_with_version_retention_period] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +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.spanner.admin.database.v1.CreateDatabaseMetadata; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; + +public class CreateDatabaseWithVersionRetentionPeriodSample { + + static void createDatabaseWithVersionRetentionPeriod() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + String versionRetentionPeriod = "7d"; + + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + createDatabaseWithVersionRetentionPeriod(adminClient, instanceId, databaseId, + versionRetentionPeriod); + } + } + + static void createDatabaseWithVersionRetentionPeriod(DatabaseAdminClient adminClient, + String instanceId, String databaseId, String versionRetentionPeriod) { + OperationFuture op = + adminClient.createDatabase( + instanceId, + databaseId, + Arrays.asList( + "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", + "ALTER DATABASE " + "`" + databaseId + "`" + + " SET OPTIONS ( version_retention_period = '" + versionRetentionPeriod + "' )" + )); + try { + Database database = op.get(); + System.out.println("Created database [" + database.getId() + "]"); + System.out.println("\tVersion retention period: " + database.getVersionRetentionPeriod()); + System.out.println("\tEarliest version time: " + database.getEarliestVersionTime()); + } 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_with_version_retention_period] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceConfigSample.java new file mode 100644 index 0000000000..3b9e49bf0e --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceConfigSample.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 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.archived; + +// [START spanner_create_instance_config] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.InstanceConfigId; +import com.google.cloud.spanner.InstanceConfigInfo; +import com.google.cloud.spanner.ReplicaInfo; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +class CreateInstanceConfigSample { + static void createInstanceConfig() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String baseInstanceConfig = "my-base-instance-config"; + String instanceConfigId = "custom-instance-config4"; + createInstanceConfig(projectId, baseInstanceConfig, instanceConfigId); + } + + static void createInstanceConfig( + String projectId, String baseInstanceConfig, String instanceConfigId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + final InstanceConfig baseConfig = instanceAdminClient.getInstanceConfig(baseInstanceConfig); + List readOnlyReplicas = + ImmutableList.of(baseConfig.getOptionalReplicas().get(0)); + InstanceConfigInfo instanceConfigInfo = + InstanceConfig.newBuilder(InstanceConfigId.of(projectId, instanceConfigId), baseConfig) + .setDisplayName(instanceConfigId) + .addReadOnlyReplicas(readOnlyReplicas) + .build(); + final OperationFuture operation = + instanceAdminClient.createInstanceConfig(instanceConfigInfo); + try { + System.out.printf("Waiting for create operation for %s to complete...\n", instanceConfigId); + InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES); + System.out.printf("Created instance configuration %s\n", instanceConfig.getId()); + } catch (ExecutionException | TimeoutException e) { + System.out.printf( + "Error: Creating instance configuration %s failed with error message %s\n", + instanceConfigInfo.getId(), e.getMessage()); + } catch (InterruptedException e) { + System.out.println( + "Error: Waiting for createInstanceConfig operation to finish was interrupted"); + } + } + } +} +// [END spanner_create_instance_config] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceExample.java similarity index 53% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceExample.java index 925b098499..15b33ae892 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,58 +14,60 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; //[START spanner_create_instance] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.CreateInstanceRequest; -import com.google.spanner.admin.instance.v1.Instance; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import com.google.spanner.admin.instance.v1.ProjectName; -import java.io.IOException; +import com.google.api.gax.longrunning.OperationFuture; +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.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; import java.util.concurrent.ExecutionException; class CreateInstanceExample { - static void createInstance() throws IOException { + static void createInstance() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; createInstance(projectId, instanceId); } - static void createInstance(String projectId, String instanceId) throws IOException { - InstanceAdminClient instanceAdminClient = InstanceAdminClient.create(); + static void createInstance(String projectId, String instanceId) { + Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); // Set Instance configuration. + String configId = "regional-us-central1"; int nodeCount = 2; String displayName = "Descriptive name"; - // Create an Instance object that will be used to create the instance. - Instance instance = - Instance.newBuilder() - .setDisplayName(displayName) + // Create an InstanceInfo object that will be used to create the instance. + InstanceInfo instanceInfo = + InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId)) + .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) .setNodeCount(nodeCount) - .setConfig( - InstanceConfigName.of(projectId, "regional-us-central1").toString()) + .setDisplayName(displayName) .build(); + OperationFuture operation = + instanceAdminClient.createInstance(instanceInfo); try { // Wait for the createInstance operation to finish. - Instance createdInstance = instanceAdminClient.createInstanceAsync( - CreateInstanceRequest.newBuilder() - .setParent(ProjectName.of(projectId).toString()) - .setInstanceId(instanceId) - .setInstance(instance) - .build()).get(); - System.out.printf("Instance %s was successfully created%n", createdInstance.getName()); + Instance instance = operation.get(); + System.out.printf("Instance %s was successfully created%n", instance.getId()); } catch (ExecutionException e) { System.out.printf( "Error: Creating instance %s failed with error message %s%n", - instance.getName(), e.getMessage()); + instanceInfo.getId(), e.getMessage()); } catch (InterruptedException e) { System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); + } finally { + spanner.close(); } } } -//[END spanner_create_instance] \ No newline at end of file +//[END spanner_create_instance] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigExample.java new file mode 100644 index 0000000000..3fe60c554b --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigExample.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 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.archived; + +// [START spanner_create_instance_with_autoscaling_config] + +import com.google.api.gax.longrunning.OperationFuture; +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.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.spanner.admin.instance.v1.AutoscalingConfig; +import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; +import java.util.concurrent.ExecutionException; + +class CreateInstanceWithAutoscalingConfigExample { + + static void createInstance() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + createInstance(projectId, instanceId); + } + + static void createInstance(String projectId, String instanceId) { + Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + + // Set Instance configuration. + String configId = "regional-us-central1"; + // Create an autoscaling config. + AutoscalingConfig autoscalingConfig = + AutoscalingConfig.newBuilder() + .setAutoscalingLimits( + AutoscalingConfig.AutoscalingLimits.newBuilder().setMinNodes(1).setMaxNodes(2)) + .setAutoscalingTargets( + AutoscalingConfig.AutoscalingTargets.newBuilder() + .setHighPriorityCpuUtilizationPercent(65) + .setStorageUtilizationPercent(95)) + .build(); + + // Create an InstanceInfo object that will be used to create the instance. + InstanceInfo instanceInfo = + InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId)) + .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) + .setAutoscalingConfig(autoscalingConfig) + .setDisplayName("Descriptive name") + .build(); + OperationFuture operation = + instanceAdminClient.createInstance(instanceInfo); + + try { + // Wait for the createInstance operation to finish. + Instance instance = operation.get(); + System.out.printf("Autoscaler instance %s was successfully created%n", instance.getId()); + } catch (ExecutionException e) { + System.out.printf( + "Error: Creating instance %s failed with error message %s%n", + instanceInfo.getId(), e.getMessage()); + } catch (InterruptedException e) { + System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); + } finally { + spanner.close(); + } + } +} +// [END spanner_create_instance_with_autoscaling_config] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithProcessingUnitsExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithProcessingUnitsExample.java new file mode 100644 index 0000000000..f688b4cdbf --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateInstanceWithProcessingUnitsExample.java @@ -0,0 +1,76 @@ +/* + * 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.archived; + +//[START spanner_create_instance_with_processing_units] + +import com.google.api.gax.longrunning.OperationFuture; +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.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; + +class CreateInstanceWithProcessingUnitsExample { + + static void createInstance() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + createInstance(projectId, instanceId); + } + + static void createInstance(String projectId, String instanceId) { + Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService(); + InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + + // Set Instance configuration. + String configId = "regional-us-central1"; + // This will create an instance with the processing power of 0.2 nodes. + int processingUnits = 500; + String displayName = "Descriptive name"; + + try { + // Creates a new instance + System.out.printf("Creating instance %s.%n", instanceId); + OperationFuture operation = + instanceAdminClient.createInstance(InstanceInfo + .newBuilder(InstanceId.of(projectId, instanceId)) + .setInstanceConfigId(InstanceConfigId.of(projectId, configId)) + .setProcessingUnits(processingUnits) + .setDisplayName(displayName) + .build()); + + // Wait for the createInstance operation to finish. + System.out.printf("Waiting for operation on %s to complete...%n", instanceId); + Instance createdInstance = operation.get(); + + System.out.printf("Created instance %s.%n", createdInstance.getId().getInstance()); + + Instance instance = instanceAdminClient.getInstance(instanceId); + System.out.printf("Instance %s has %d processing units.%n", instance.getId().getInstance(), + instance.getProcessingUnits()); + } catch (Exception e) { + System.out.printf("Error: %s.%n", e.getMessage()); + } + spanner.close(); + } +} +//[END spanner_create_instance_with_processing_units] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateSequenceSample.java similarity index 87% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateSequenceSample.java index 6614d988b7..637bd39d9a 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateSequenceSample.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_create_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,18 +25,14 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class CreateSequenceSample { - - static void createSequence() throws IOException { + static void createSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -44,21 +40,21 @@ static void createSequence() throws IOException { createSequence(projectId, instanceId, databaseId); } - static void createSequence(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void createSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); - databaseAdminClient - .updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "CREATE SEQUENCE Seq OPTIONS (sequence_kind = 'bit_reversed_positive')", "CREATE TABLE Customers (CustomerId INT64 DEFAULT " + "(GET_NEXT_SEQUENCE_VALUE(SEQUENCE Seq)), CustomerName STRING(1024)) " - + "PRIMARY KEY (CustomerId)")) + + "PRIMARY KEY (CustomerId)"), + null) .get(5, TimeUnit.MINUTES); System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSample.java similarity index 72% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSample.java index cc46f1e214..a5cc4668d5 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSample.java @@ -14,32 +14,34 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_create_table_with_foreign_key_delete_cascade] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; class CreateTableWithForeignKeyDeleteCascadeSample { - static void createForeignKeyDeleteCascadeConstraint() throws IOException { + static void createForeignKeyDeleteCascadeConstraint() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - createForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + createForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); + } } static void createForeignKeyDeleteCascadeConstraint( - String projectId, String instanceId, String databaseId) throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), + DatabaseAdminClient adminClient, String instanceId, String databaseId) { + adminClient.updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "CREATE TABLE Customers (\n" + " CustomerId INT64 NOT NULL,\n" @@ -51,7 +53,8 @@ static void createForeignKeyDeleteCascadeConstraint( + " CustomerName STRING(62) NOT NULL,\n" + " CONSTRAINT FKShoppingCartsCustomerId FOREIGN KEY (CustomerId)\n" + " REFERENCES Customers (CustomerId) ON DELETE CASCADE\n" - + " ) PRIMARY KEY (CartId)\n")); + + " ) PRIMARY KEY (CartId)\n"), + null); System.out.printf( String.format( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DeleteInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DeleteInstanceConfigSample.java similarity index 57% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/DeleteInstanceConfigSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/DeleteInstanceConfigSample.java index 1fa9aee586..b1013cd4b0 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DeleteInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DeleteInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,40 +14,34 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_delete_instance_config] - +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.DeleteInstanceConfigRequest; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import java.io.IOException; +import com.google.cloud.spanner.SpannerOptions; class DeleteInstanceConfigSample { - - static void deleteInstanceConfig() throws IOException { + static void deleteInstanceConfig() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceConfigId = "custom-user-config"; deleteInstanceConfig(projectId, instanceConfigId); } - static void deleteInstanceConfig(String projectId, String instanceConfigId) throws IOException { - try (final InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, - instanceConfigId); - final DeleteInstanceConfigRequest request = - DeleteInstanceConfigRequest.newBuilder().setName(instanceConfigName.toString()).build(); - + static void deleteInstanceConfig(String projectId, String instanceConfigId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); try { - System.out.printf("Deleting %s...\n", instanceConfigName); - instanceAdminClient.deleteInstanceConfig(request); - System.out.printf("Deleted instance configuration %s\n", instanceConfigName); + System.out.printf("Deleting %s...\n", instanceConfigId); + instanceAdminClient.deleteInstanceConfig(instanceConfigId); + System.out.printf("Deleted instance configuration %s\n", instanceConfigId); } catch (SpannerException e) { System.out.printf( "Error: Deleting instance configuration %s failed with error message: %s\n", - instanceConfigName, e.getMessage()); + instanceConfigId, e.getMessage()); } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSample.java similarity index 67% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSample.java index 7d569acea8..bf03542f91 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSample.java @@ -14,34 +14,38 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_drop_foreign_key_constraint_delete_cascade] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; class DropForeignKeyConstraintDeleteCascadeSample { - static void deleteForeignKeyDeleteCascadeConstraint() throws IOException { + static void deleteForeignKeyDeleteCascadeConstraint() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceId = "my-instance"; String databaseId = "my-database"; - deleteForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId); + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + deleteForeignKeyDeleteCascadeConstraint(adminClient, instanceId, databaseId); + } } static void deleteForeignKeyDeleteCascadeConstraint( - String projectId, String instanceId, String databaseId) throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), + DatabaseAdminClient adminClient, String instanceId, String databaseId) { + adminClient.updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "ALTER TABLE ShoppingCarts\n" - + " DROP CONSTRAINT FKShoppingCartsCustomerName\n")); + + " DROP CONSTRAINT FKShoppingCartsCustomerName\n"), + null); System.out.printf( String.format( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropSequenceSample.java similarity index 78% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/DropSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/DropSequenceSample.java index f9917677de..3e8f2bb414 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/DropSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/DropSequenceSample.java @@ -14,22 +14,20 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_drop_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class DropSequenceSample { - - static void dropSequence() throws IOException { + static void dropSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -37,16 +35,21 @@ static void dropSequence() throws IOException { dropSequence(projectId, instanceId, databaseId); } - static void dropSequence(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - try { - databaseAdminClient - .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId), + static void dropSequence(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT", - "DROP SEQUENCE Seq")) + "DROP SEQUENCE Seq"), + null) .get(5, TimeUnit.MINUTES); + System.out.println( "Altered Customers table to drop DEFAULT from CustomerId column " + "and dropped the Seq sequence"); diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/EnableFineGrainedAccess.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/EnableFineGrainedAccess.java new file mode 100644 index 0000000000..cc373f50f6 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/EnableFineGrainedAccess.java @@ -0,0 +1,102 @@ +/* + * 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.archived; + +// [START spanner_enable_fine_grained_access] +import com.google.cloud.Binding; +import com.google.cloud.Condition; +import com.google.cloud.Policy; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.collect.ImmutableList; + +public class EnableFineGrainedAccess { + + static void enableFineGrainedAccess() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + String iamMember = "user:alice@example.com"; + String role = "my-role"; + String title = "my-condition-title"; + enableFineGrainedAccess(projectId, instanceId, databaseId, iamMember, title, role); + } + + static void enableFineGrainedAccess( + String projectId, + String instanceId, + String databaseId, + String iamMember, + String title, + String role) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + Policy policy = adminClient.getDatabaseIAMPolicy(instanceId, databaseId, 3); + int policyVersion = policy.getVersion(); + // The policy in the response from getDatabaseIAMPolicy might use the policy version + // that you specified, or it might use a lower policy version. For example, if you + // specify version 3, but the policy has no conditional role bindings, the response + // uses version 1. Valid values are 0, 1, and 3. + if (policy.getVersion() < 3) { + // conditional role bindings work with policy version 3 + policyVersion = 3; + } + + Binding binding1 = + Binding.newBuilder() + .setRole("roles/spanner.fineGrainedAccessUser") + .setMembers(ImmutableList.of(iamMember)) + .build(); + + Binding binding2 = + Binding.newBuilder() + .setRole("roles/spanner.databaseRoleUser") + .setCondition( + Condition.newBuilder() + .setDescription(title) + .setExpression( + String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role)) + .setTitle(title) + .build()) + .setMembers(ImmutableList.of(iamMember)) + .build(); + ImmutableList bindings = + ImmutableList.builder() + .addAll(policy.getBindingsList()) + .add(binding1) + .add(binding2) + .build(); + Policy policyWithConditions = + Policy.newBuilder() + .setVersion(policyVersion) + .setEtag(policy.getEtag()) + .setBindings(bindings) + .build(); + Policy response = + adminClient.setDatabaseIAMPolicy(instanceId, databaseId, policyWithConditions); + System.out.printf( + "Enabled fine-grained access in IAM with version %d%n", response.getVersion()); + } + } +} +// [END spanner_enable_fine_grained_access] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetDatabaseDdlSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetDatabaseDdlSample.java similarity index 56% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/GetDatabaseDdlSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/GetDatabaseDdlSample.java index 250136c1fb..c7f56f4969 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetDatabaseDdlSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetDatabaseDdlSample.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; //[START spanner_get_database_ddl] -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.spanner.admin.database.v1.GetDatabaseDdlResponse; -import java.io.IOException; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import java.util.List; public class GetDatabaseDdlSample { - static void getDatabaseDdl() throws IOException { + static void getDatabaseDdl() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -34,15 +34,18 @@ static void getDatabaseDdl() throws IOException { } static void getDatabaseDdl( - String projectId, String instanceId, String databaseId) throws IOException { - - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - final GetDatabaseDdlResponse response = - databaseAdminClient.getDatabaseDdl(DatabaseName.of(projectId, instanceId, databaseId)); - System.out.println("Retrieved database DDL for " + databaseId); - for (String ddl : response.getStatementsList()) { - System.out.println(ddl); + String projectId, String instanceId, String databaseId) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + final List ddls = databaseAdminClient.getDatabaseDdl(instanceId, databaseId); + System.out.println("Retrieved database DDL for " + databaseId); + for (String ddl : ddls) { + System.out.println(ddl); + } } } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetInstanceConfigSample.java similarity index 52% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/GetInstanceConfigSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/GetInstanceConfigSample.java index 5fc1b90747..38b6230b97 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/GetInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/GetInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -14,36 +14,39 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; //[START spanner_get_instance_config] -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.InstanceConfig; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import java.io.IOException; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; public class GetInstanceConfigSample { - static void getInstanceConfig() throws IOException { + static void getInstanceConfig() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; - final String instanceConfigId = "nam6"; - getInstanceConfig(projectId, instanceConfigId); + final String instanceConfigName = "nam6"; + getInstanceConfig(projectId, instanceConfigName); } - static void getInstanceConfig(String projectId, String instanceConfigId) throws IOException { - try (final InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, - instanceConfigId); + static void getInstanceConfig(String projectId, String instanceConfigName) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); - final InstanceConfig instanceConfig = - instanceAdminClient.getInstanceConfig(instanceConfigName.toString()); + final InstanceConfig instanceConfig = instanceAdminClient + .getInstanceConfig(instanceConfigName); System.out.printf( "Available leader options for instance config %s: %s%n", - instanceConfig.getName(), - instanceConfig.getLeaderOptionsList() + instanceConfig.getId(), + instanceConfig.getLeaderOptions() ); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabaseRoles.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabaseRoles.java new file mode 100644 index 0000000000..ee31d874ec --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabaseRoles.java @@ -0,0 +1,52 @@ +/* + * 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.archived; + +// [START spanner_list_database_roles] +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseRole; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import java.util.concurrent.ExecutionException; + +public class ListDatabaseRoles { + + static void listDatabaseRoles() throws InterruptedException, ExecutionException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + listDatabaseRoles(projectId, instanceId, databaseId); + } + + static void listDatabaseRoles(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + String databasePath = DatabaseId.of(projectId, instanceId, databaseId).getName(); + System.out.println("List of Database roles"); + for (DatabaseRole role : adminClient.listDatabaseRoles(instanceId, databaseId).iterateAll()) { + System.out.printf("%s%n", role.getName()); + } + } + } +} +// [END spanner_list_database_roles] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabasesSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabasesSample.java new file mode 100644 index 0000000000..4abd2c6f25 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListDatabasesSample.java @@ -0,0 +1,56 @@ +/* + * 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.archived; + +//[START spanner_list_databases] + +import com.google.api.gax.paging.Page; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; + +public class ListDatabasesSample { + + static void listDatabases() { + // TODO(developer): Replace these variables before running the sample. + final String projectId = "my-project"; + final String instanceId = "my-instance"; + listDatabases(projectId, instanceId); + } + + static void listDatabases(String projectId, String instanceId) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + Page page = databaseAdminClient.listDatabases(instanceId); + System.out.println("Databases for projects/" + projectId + "/instances/" + instanceId); + while (page != null) { + for (Database database : page.iterateAll()) { + final String defaultLeader = database.getDefaultLeader().equals("") + ? "" : "(default leader = " + database.getDefaultLeader() + ")"; + System.out.println("\t" + database.getId() + " " + defaultLeader); + } + page = page.getNextPage(); + } + } + } +} +//[END spanner_list_databases] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigOperationsSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigOperationsSample.java new file mode 100644 index 0000000000..2cf683b4c9 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigOperationsSample.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 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.archived; + +// [START spanner_list_instance_config_operations] +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.Options; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.longrunning.Operation; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; + +public class ListInstanceConfigOperationsSample { + static void listInstanceConfigOperations() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + listInstanceConfigOperations(projectId); + } + + static void listInstanceConfigOperations(String projectId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + + try { + System.out.printf( + "Getting list of instance config operations for project %s...\n", + projectId); + final Iterable instanceConfigOperations = + instanceAdminClient + .listInstanceConfigOperations( + Options.filter( + "(metadata.@type=type.googleapis.com/" + + "google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)")) + .iterateAll(); + for (Operation operation : instanceConfigOperations) { + CreateInstanceConfigMetadata metadata = + operation.getMetadata().unpack(CreateInstanceConfigMetadata.class); + System.out.printf( + "Create instance config operation for %s is %d%% completed.\n", + metadata.getInstanceConfig().getName(), metadata.getProgress().getProgressPercent()); + } + } catch (InvalidProtocolBufferException e) { + System.out.printf( + "Error: Listing instance config operations failed with error message %s\n", + e.getMessage()); + } + } + } +} +// [END spanner_list_instance_config_operations] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigsSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigsSample.java similarity index 53% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigsSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigsSample.java index 92a0c7014e..b7753502ba 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigsSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/ListInstanceConfigsSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -14,32 +14,36 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; //[START spanner_list_instance_configs] -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.InstanceConfig; -import com.google.spanner.admin.instance.v1.ProjectName; -import java.io.IOException; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; public class ListInstanceConfigsSample { - static void listInstanceConfigs() throws IOException { + static void listInstanceConfigs() { // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; + final String projectId = "my-project"; listInstanceConfigs(projectId); } - static void listInstanceConfigs(String projectId) throws IOException { - try (final InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final ProjectName projectName = ProjectName.of(projectId); - for (InstanceConfig instanceConfig : - instanceAdminClient.listInstanceConfigs(projectName).iterateAll()) { + static void listInstanceConfigs(String projectId) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + + for (InstanceConfig instanceConfig : instanceAdminClient.listInstanceConfigs().iterateAll()) { System.out.printf( "Available leader options for instance config %s: %s%n", - instanceConfig.getName(), - instanceConfig.getLeaderOptionsList() + instanceConfig.getId(), + instanceConfig.getLeaderOptions() ); } } diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgAlterSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgAlterSequenceSample.java similarity index 86% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/PgAlterSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/PgAlterSequenceSample.java index 39a4cd543a..10bccc2cb4 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgAlterSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgAlterSequenceSample.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_alter_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,18 +25,14 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgAlterSequenceSample { - - static void pgAlterSequence() throws IOException { + static void pgAlterSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -44,15 +40,16 @@ static void pgAlterSequence() throws IOException { pgAlterSequence(projectId, instanceId, databaseId); } - static void pgAlterSequence(String projectId, String instanceId, String databaseId) - throws IOException { + static void pgAlterSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - databaseAdminClient - .updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of("ALTER SEQUENCE Seq SKIP RANGE 1000 5000000")) + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER SEQUENCE Seq SKIP RANGE 1000 5000000"), + null) .get(5, TimeUnit.MINUTES); System.out.println( "Altered Seq sequence to skip an inclusive range between 1000 and 5000000"); diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCaseSensitivitySample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCaseSensitivitySample.java similarity index 80% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCaseSensitivitySample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCaseSensitivitySample.java index 951b46446f..a9096c0c2e 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCaseSensitivitySample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCaseSensitivitySample.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_identifier_case_sensitivity] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.Mutation; @@ -26,16 +28,13 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.common.collect.Lists; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.Collections; import java.util.concurrent.ExecutionException; public class PgCaseSensitivitySample { - static void pgCaseSensitivity() throws IOException { + static void pgCaseSensitivity() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -43,33 +42,35 @@ static void pgCaseSensitivity() throws IOException { pgCaseSensitivity(projectId, instanceId, databaseId); } - static void pgCaseSensitivity(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void pgCaseSensitivity(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder() .setProjectId(projectId) .build() .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); // Spanner PostgreSQL follows the case sensitivity rules of PostgreSQL. This means that: // 1. Identifiers that are not double-quoted are folded to lower case. // 2. Identifiers that are double-quoted retain their case and are case-sensitive. // See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS // for more information. - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - Lists.newArrayList( - "CREATE TABLE Singers (" - // SingerId will be folded to `singerid`. - + " SingerId bigint NOT NULL PRIMARY KEY," - // FirstName and LastName are double-quoted and will therefore retain their - // mixed case and are case-sensitive. This means that any statement that - // references any of these columns must use double quotes. - + " \"FirstName\" varchar(1024) NOT NULL," - + " \"LastName\" varchar(1024) NOT NULL" - + ")")).get(); + final OperationFuture updateOperation = + databaseAdminClient.updateDatabaseDdl( + instanceId, + databaseId, + Collections.singleton( + "CREATE TABLE Singers (" + // SingerId will be folded to `singerid`. + + " SingerId bigint NOT NULL PRIMARY KEY," + // FirstName and LastName are double-quoted and will therefore retain their + // mixed case and are case-sensitive. This means that any statement that + // references any of these columns must use double quotes. + + " \"FirstName\" varchar(1024) NOT NULL," + + " \"LastName\" varchar(1024) NOT NULL" + + ")"), + null); + updateOperation.get(); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCreateSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCreateSequenceSample.java similarity index 85% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCreateSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCreateSequenceSample.java index 6e7d9c34c5..070f62f70f 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgCreateSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgCreateSequenceSample.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_create_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -25,18 +25,14 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgCreateSequenceSample { - - static void pgCreateSequence() throws IOException { + static void pgCreateSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -44,22 +40,24 @@ static void pgCreateSequence() throws IOException { pgCreateSequence(projectId, instanceId, databaseId); } - static void pgCreateSequence(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void pgCreateSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - databaseAdminClient - .updateDatabaseDdlAsync(DatabaseName.of(projectId, instanceId, databaseId).toString(), + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "CREATE SEQUENCE Seq BIT_REVERSED_POSITIVE;", "CREATE TABLE Customers (CustomerId BIGINT DEFAULT nextval('Seq'), " - + "CustomerName character varying(1024), PRIMARY KEY (CustomerId))")) + + "CustomerName character varying(1024), PRIMARY KEY (CustomerId))"), + null) .get(5, TimeUnit.MINUTES); System.out.println( - "Created Seq sequence and Customers table, where the key column " + "Created Seq sequence and Customers table, where its key column " + "CustomerId uses the sequence as a default value"); final DatabaseClient dbClient = diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgDropSequenceSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgDropSequenceSample.java similarity index 81% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/PgDropSequenceSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/PgDropSequenceSample.java index dec927d20a..26e8eb74bb 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgDropSequenceSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgDropSequenceSample.java @@ -14,24 +14,20 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_drop_sequence] - +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Spanner; 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.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class PgDropSequenceSample { - - static void pgDropSequence() throws IOException { + static void pgDropSequence() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -39,18 +35,18 @@ static void pgDropSequence() throws IOException { pgDropSequence(projectId, instanceId, databaseId); } - static void pgDropSequence(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - + static void pgDropSequence(String projectId, String instanceId, String databaseId) { try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { - databaseAdminClient - .updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), + final DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, ImmutableList.of( "ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT", - "DROP SEQUENCE Seq")) + "DROP SEQUENCE Seq"), + null) .get(5, TimeUnit.MINUTES); System.out.println( "Altered Customers table to drop DEFAULT from " diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgInterleavedTableSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgInterleavedTableSample.java similarity index 57% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/PgInterleavedTableSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/PgInterleavedTableSample.java index 14af53dc5a..007843a4a2 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/PgInterleavedTableSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgInterleavedTableSample.java @@ -14,20 +14,22 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_postgresql_interleaved_table] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; +import com.google.cloud.spanner.SpannerOptions; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.Arrays; import java.util.concurrent.ExecutionException; public class PgInterleavedTableSample { - static void pgInterleavedTable() throws IOException { + static void pgInterleavedTable() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -35,29 +37,36 @@ static void pgInterleavedTable() throws IOException { pgInterleavedTable(projectId, instanceId, databaseId); } - static void pgInterleavedTable(String projectId, String instanceId, String databaseId) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - try { + static void pgInterleavedTable(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + // The Spanner PostgreSQL dialect extends the PostgreSQL dialect with certain Spanner // specific features, such as interleaved tables. // See https://cloud.google.com/spanner/docs/postgresql/data-definition-language#create_table // for the full CREATE TABLE syntax. - databaseAdminClient.updateDatabaseDdlAsync(DatabaseName.of(projectId, + final OperationFuture updateOperation = + databaseAdminClient.updateDatabaseDdl( instanceId, - databaseId), - Arrays.asList( - "CREATE TABLE Singers (" - + " SingerId bigint NOT NULL PRIMARY KEY," - + " FirstName varchar(1024) NOT NULL," - + " LastName varchar(1024) NOT NULL" - + ")", - "CREATE TABLE Albums (" - + " SingerId bigint NOT NULL," - + " AlbumId bigint NOT NULL," - + " Title varchar(1024) NOT NULL," - + " PRIMARY KEY (SingerId, AlbumId)" - + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get(); + databaseId, + Arrays.asList( + "CREATE TABLE Singers (" + + " SingerId bigint NOT NULL PRIMARY KEY," + + " FirstName varchar(1024) NOT NULL," + + " LastName varchar(1024) NOT NULL" + + ")", + "CREATE TABLE Albums (" + + " SingerId bigint NOT NULL," + + " AlbumId bigint NOT NULL," + + " Title varchar(1024) NOT NULL," + + " PRIMARY KEY (SingerId, AlbumId)" + + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE"), + null); + updateOperation.get(); System.out.println("Created interleaved table hierarchy using PostgreSQL dialect"); } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgSpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgSpannerSample.java new file mode 100644 index 0000000000..8a95cebef0 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/PgSpannerSample.java @@ -0,0 +1,1585 @@ +/* + * 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.archived; + +import com.google.api.gax.longrunning.OperationFuture; +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.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.Instance; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceId; +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.common.io.BaseEncoding; +import com.google.longrunning.Operation; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.spanner.admin.database.v1.CreateBackupMetadata; +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; +import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.v1.ExecuteSqlRequest; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +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, DatabaseId id) { + OperationFuture op = dbAdminClient.createDatabase( + dbAdminClient.newDatabaseBuilder(id).setDialect(Dialect.POSTGRESQL).build(), + Collections.emptyList()); + try { + // Initiate the request which returns an OperationFuture. + Database db = op.get(); + System.out.println("Created database [" + db.getId() + "]"); + createTableUsingDdl(dbAdminClient, id); + } 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, DatabaseId dbId) { + OperationFuture op = adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId dbId) { + OperationFuture op = adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList( + "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) " + + "INCLUDE (MarketingBudget)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId id) { + OperationFuture op = + dbAdminClient.updateDatabaseDdl( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.get(); + System.out.println("Created Singers & Albums tables in database: [" + id + "]"); + } 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] + // [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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList( + "ALTER TABLE Albums ADD COLUMN LastUpdateTime spanner.commit_timestamp"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId id) { + OperationFuture op = + dbAdminClient.updateDatabaseDdl( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.get(); + System.out.println("Created Performances table in database: [" + id + "]"); + } 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, DatabaseId id) { + OperationFuture op = + dbAdminClient.updateDatabaseDdl( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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))"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.get(); + System.out.println("Created Venues table in database: [" + id + "]"); + } 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(InstanceAdminClient instanceAdminClient, DatabaseId databaseId) { + Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); + // Get create backup operations for the sample database. + Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( + TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, + TimeUnit.HOURS), 0); + String filter = + String.format( + "(metadata.database:%s) AND " + + "(metadata.@type:type.googleapis.com/" + + "google.spanner.admin.database.v1.CreateBackupMetadata) AND " + + "(metadata.progress.start_time > \"%s\")", + databaseId.getName(), last24Hours); + Page operations = instance + .listBackupOperations(Options.filter(filter)); + for (com.google.longrunning.Operation op : operations.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()); + } + } + } + // [END spanner_postgresql_list_backup_operations] + + // [START spanner_postgresql_list_database_operations] + static void listDatabaseOperations( + InstanceAdminClient instanceAdminClient, + DatabaseAdminClient dbAdminClient, + InstanceId instanceId) { + Instance instance = instanceAdminClient.getInstance(instanceId.getInstance()); + // Get optimize restored database operations. + Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( + TimeUnit.HOURS.convert(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); + for (Operation op : instance.listDatabaseOperations(Options.filter(filter)).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, + InstanceAdminClient instanceAdminClient, + String command, + DatabaseId database) { + switch (command) { + case "createdatabase": + createPostgreSqlDatabase(dbAdminClient, database); + 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, database); + break; + case "update": + update(dbClient); + break; + case "writetransaction": + writeWithTransaction(dbClient); + break; + case "querymarketingbudget": + queryMarketingBudget(dbClient); + break; + case "addindex": + addIndex(dbAdminClient, database); + break; + case "readindex": + readUsingIndex(dbClient); + break; + case "addstoringindex": + addStoringIndex(dbAdminClient, database); + 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, database); + break; + case "readstaledata": + readStaleData(dbClient); + break; + case "addlastupdatetimestampcolumn": + addLastUpdateTimestampColumn(dbAdminClient, database); + break; + case "updatewithtimestamp": + updateWithTimestamp(dbClient); + break; + case "querywithtimestamp": + queryMarketingBudgetWithTimestamp(dbClient); + break; + case "createtablewithtimestamp": + createTableWithTimestamp(dbAdminClient, database); + 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, database); + 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(instanceAdminClient, database); + break; + case "listdatabaseoperations": + listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId()); + 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) { + if (args.length != 3) { + printUsageAndExit(); + } + // [START spanner_init_client] + SpannerOptions options = SpannerOptions.newBuilder().build(); + Spanner spanner = options.getService(); + try { + // [END spanner_init_client] + 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(); + } + // [START spanner_init_client] + DatabaseClient dbClient = spanner.getDatabaseClient(db); + DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + // [END spanner_init_client] + + // Use client here... + run(dbClient, dbAdminClient, instanceAdminClient, command, db); + // [START spanner_init_client] + } finally { + 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/archived/RestoreBackupWithEncryptionKey.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/RestoreBackupWithEncryptionKey.java new file mode 100644 index 0000000000..087413dbe5 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/RestoreBackupWithEncryptionKey.java @@ -0,0 +1,92 @@ +/* + * 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.archived; + +// [START spanner_restore_backup_with_encryption_key] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Restore; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.encryption.EncryptionConfigs; +import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; +import java.util.concurrent.ExecutionException; + +public class RestoreBackupWithEncryptionKey { + + static void restoreBackupWithEncryptionKey() { + // 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 (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + restoreBackupWithEncryptionKey( + adminClient, + projectId, + instanceId, + backupId, + databaseId, + kmsKeyName); + } + } + + static Void restoreBackupWithEncryptionKey(DatabaseAdminClient adminClient, + String projectId, String instanceId, String backupId, String restoreId, String kmsKeyName) { + final Restore restore = adminClient + .newRestoreBuilder( + BackupId.of(projectId, instanceId, backupId), + DatabaseId.of(projectId, instanceId, restoreId)) + .setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName)) + .build(); + final OperationFuture operation = adminClient + .restoreDatabase(restore); + + Database database; + try { + System.out.println("Waiting for operation to complete..."); + database = operation.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().getSourceDatabase(), + database.getId(), + database.getRestoreInfo().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/archived/SpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/SpannerSample.java new file mode 100644 index 0000000000..98eba6d944 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/SpannerSample.java @@ -0,0 +1,2233 @@ +/* + * 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.archived; + +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.paging.Page; +import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.rpc.StatusCode; +import com.google.cloud.ByteArray; +import com.google.cloud.Date; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Instance; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceId; +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.RestoreInfo; +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.common.io.BaseEncoding; +import com.google.longrunning.Operation; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.spanner.admin.database.v1.CopyBackupMetadata; +import com.google.spanner.admin.database.v1.CreateBackupMetadata; +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; +import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata; +import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.threeten.bp.LocalDate; +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.temporal.ChronoField; + +/** + * 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; + } + } + + /** Get a database id to restore a backup to from the sample database id. */ + static String createRestoredSampleDbId(DatabaseId database) { + int index = database.getDatabase().indexOf('-'); + String prefix = database.getDatabase().substring(0, index); + String restoredDbId = database.getDatabase().replace(prefix, "restored"); + if (restoredDbId.length() > 30) { + restoredDbId = restoredDbId.substring(0, 30); + } + return restoredDbId; + } + + // [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, DatabaseId id) { + OperationFuture op = + dbAdminClient.createDatabase( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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")); + try { + // Initiate the request which returns an OperationFuture. + Database db = op.get(); + System.out.println("Created database [" + db.getId() + "]"); + } 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, DatabaseId id) { + OperationFuture op = + dbAdminClient.updateDatabaseDdl( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.get(); + System.out.println("Created Performances table in database: [" + id + "]"); + } 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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList( + "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) " + + "STORING (MarketingBudget)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId dbId) { + OperationFuture op = + adminClient.updateDatabaseDdl( + dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList( + "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP " + + "OPTIONS (allow_commit_timestamp=true)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.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, DatabaseId id) { + OperationFuture op = + dbAdminClient.updateDatabaseDdl( + id.getInstanceId().getInstance(), + id.getDatabase(), + 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)"), + null); + try { + // Initiate the request which returns an OperationFuture. + op.get(); + System.out.println("Created Venues table in database: [" + id + "]"); + } 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, DatabaseId databaseId, + BackupId backupId, Timestamp versionTime) { + // Set expire time to 14 days from now. + Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( + System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); + Backup backup = + dbAdminClient + .newBackupBuilder(backupId) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + // Initiate the request which returns an OperationFuture. + System.out.println("Creating backup [" + backup.getId() + "]..."); + OperationFuture op = backup.create(); + try { + // Wait for the backup operation to complete. + backup = op.get(); + System.out.println("Created backup [" + backup.getId() + "]"); + } catch (ExecutionException e) { + throw (SpannerException) e.getCause(); + } catch (InterruptedException e) { + throw SpannerExceptionFactory.propagateInterrupt(e); + } + + // Reload the metadata of the backup from the server. + backup = backup.reload(); + System.out.println( + String.format( + "Backup %s of size %d bytes was created at %s for version of database at %s", + backup.getId().getName(), + backup.getSize(), + LocalDateTime.ofEpochSecond( + backup.getProto().getCreateTime().getSeconds(), + backup.getProto().getCreateTime().getNanos(), + OffsetDateTime.now().getOffset()), + LocalDateTime.ofEpochSecond( + backup.getProto().getVersionTime().getSeconds(), + backup.getProto().getVersionTime().getNanos(), + OffsetDateTime.now().getOffset()) + )); + } + // [END spanner_create_backup] + + // [START spanner_cancel_backup_create] + static void cancelCreateBackup( + DatabaseAdminClient dbAdminClient, DatabaseId databaseId, BackupId backupId) { + // Set expire time to 14 days from now. + Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert( + System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS)); + + // Create a backup instance. + Backup backup = + dbAdminClient + .newBackupBuilder(backupId) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .build(); + // Start the creation of a backup. + System.out.println("Creating backup [" + backup.getId() + "]..."); + OperationFuture op = backup.create(); + try { + // Try to cancel the backup operation. + System.out.println("Cancelling create backup operation for [" + backup.getId() + "]..."); + dbAdminClient.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. + backup.delete(); + System.out.println("Backup operation for [" + backup.getId() + + "] successfully finished before it could be cancelled"); + } else if (pollingFuture.get().getErrorCode().getCode() == StatusCode.Code.CANCELLED) { + System.out.println("Backup operation for [" + backup.getId() + "] 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( + InstanceAdminClient instanceAdminClient, DatabaseId databaseId, BackupId backupId) { + Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); + // Get create backup operations for the sample database. + Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( + TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24, + TimeUnit.HOURS), 0); + String filter = + String.format( + "(metadata.@type:type.googleapis.com/" + + "google.spanner.admin.database.v1.CreateBackupMetadata) " + + "AND (metadata.database:%s)", + databaseId.getName()); + Page createBackupOperations = instance.listBackupOperations( + Options.filter(filter)); + 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)", + backupId.getName()); + Page copyBackupOperations = instance.listBackupOperations(Options.filter(filter)); + 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( + InstanceAdminClient instanceAdminClient, + DatabaseAdminClient dbAdminClient, + InstanceId instanceId) { + Instance instance = instanceAdminClient.getInstance(instanceId.getInstance()); + // Get optimize restored database operations. + Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert( + TimeUnit.HOURS.convert(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); + for (Operation op : instance.listDatabaseOperations(Options.filter(filter)).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( + InstanceAdminClient instanceAdminClient, DatabaseId databaseId, BackupId backupId) { + Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance()); + // List all backups. + System.out.println("All backups:"); + for (Backup backup : instance.listBackups().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.getBackup())); + for (Backup backup : instance.listBackups( + Options.filter(String.format("name:%s", backupId.getBackup()))).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.getDatabase())); + for (Backup backup : instance.listBackups( + Options.filter(String.format("database:%s", databaseId.getDatabase()))).iterateAll()) { + System.out.println(backup); + } + + // List all backups that expire before a certain time. + Timestamp expireTime = 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.toString())); + for (Backup backup : + instance.listBackups( + Options.filter(String.format("expire_time < \"%s\"", expireTime.toString()))) + .iterateAll()) { + System.out.println(backup); + } + + // List all backups with size greater than a certain number of bytes. + System.out.println("All backups with size greater than 100 bytes:"); + for (Backup backup : instance.listBackups(Options.filter("size_bytes > 100")).iterateAll()) { + System.out.println(backup); + } + + // List all backups with a create time after a certain timestamp and that are also ready. + Timestamp createTime = 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())); + for (Backup backup : + instance + .listBackups(Options.filter( + String.format("create_time >= \"%s\" AND state:READY", createTime.toString()))) + .iterateAll()) { + System.out.println(backup); + } + + // List backups using pagination. + System.out.println("All backups, listed using pagination:"); + Page page = instance.listBackups(Options.pageSize(10)); + while (true) { + for (Backup backup : page.getValues()) { + System.out.println(backup); + } + if (!page.hasNextPage()) { + break; + } + page = page.getNextPage(); + } + } + // [END spanner_list_backups] + + // [START spanner_restore_backup] + static void restoreBackup( + DatabaseAdminClient dbAdminClient, + BackupId backupId, + DatabaseId sourceDatabaseId, + DatabaseId restoreToDatabase) { + Backup backup = dbAdminClient.newBackupBuilder(backupId).build(); + // Initiate the request which returns an OperationFuture. + System.out.println(String.format( + "Restoring backup [%s] to database [%s]...", + backup.getId().toString(), + restoreToDatabase.toString())); + try { + OperationFuture op = backup.restore(restoreToDatabase); + // Wait until the database has been restored. + Database db = op.get(); + // Refresh database metadata and get the restore info. + RestoreInfo restore = db.reload().getRestoreInfo(); + Timestamp versionTime = Timestamp.fromProto(restore + .getProto() + .getBackupInfo() + .getVersionTime()); + System.out.println( + "Restored database [" + + restore.getSourceDatabase().getName() + + "] from [" + + restore.getBackup().getName() + + "] with version time [" + versionTime + "]"); + } 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, BackupId backupId) { + // Get current backup metadata. + Backup backup = dbAdminClient.newBackupBuilder(backupId).build().reload(); + // Add 30 days to the expire time. + // Expire time must be within 366 days of the create time of the backup. + Timestamp expireTime = + Timestamp.ofTimeMicroseconds( + TimeUnit.SECONDS.toMicros(backup.getExpireTime().getSeconds()) + + TimeUnit.NANOSECONDS.toMicros(backup.getExpireTime().getNanos()) + + TimeUnit.DAYS.toMicros(30L)); + // New Expire Time must be less than Max Expire Time + expireTime = expireTime.compareTo(backup.getMaxExpireTime()) + < 0 ? expireTime : backup.getMaxExpireTime(); + int timeDiff = expireTime.compareTo(backup.getExpireTime()); + Timestamp newExpireTime = (timeDiff < 0) ? expireTime : backup.getExpireTime(); + + System.out.println(String.format( + "Updating expire time of backup [%s] to %s...", + backupId.toString(), + LocalDateTime.ofEpochSecond( + expireTime.getSeconds(), + expireTime.getNanos(), + OffsetDateTime.now().getOffset()).toString())); + + // Update expire time. + backup = backup.toBuilder().setExpireTime(expireTime).build(); + backup.updateExpireTime(); + System.out.println("Updated backup [" + backupId + "]"); + } + // [END spanner_update_backup] + + // [START spanner_delete_backup] + static void deleteBackup(DatabaseAdminClient dbAdminClient, BackupId backupId) { + Backup backup = dbAdminClient.newBackupBuilder(backupId).build(); + // Delete the backup. + System.out.println("Deleting backup [" + backupId + "]..."); + backup.delete(); + // Verify that the backup is deleted. + if (backup.exists()) { + System.out.println("Delete backup [" + backupId + "] failed"); + throw new RuntimeException("Delete backup [" + backupId + "] failed"); + } else { + System.out.println("Deleted backup [" + backupId + "]"); + } + } + // [END spanner_delete_backup] + + static void run( + DatabaseClient dbClient, + DatabaseAdminClient dbAdminClient, + InstanceAdminClient instanceAdminClient, + String command, + DatabaseId database, + BackupId backup) { + switch (command) { + case "createdatabase": + createDatabase(dbAdminClient, database); + 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, database); + break; + case "update": + update(dbClient); + break; + case "writetransaction": + writeWithTransaction(dbClient); + break; + case "querymarketingbudget": + queryMarketingBudget(dbClient); + break; + case "addindex": + addIndex(dbAdminClient, database); + break; + case "readindex": + readUsingIndex(dbClient); + break; + case "queryindex": + queryUsingIndex(dbClient); + break; + case "addstoringindex": + addStoringIndex(dbAdminClient, database); + break; + case "readstoringindex": + readStoringIndex(dbClient); + break; + case "readonlytransaction": + readOnlyTransaction(dbClient); + break; + case "readstaledata": + readStaleData(dbClient); + break; + case "addcommittimestamp": + addCommitTimestamp(dbAdminClient, database); + break; + case "updatewithtimestamp": + updateWithTimestamp(dbClient); + break; + case "querywithtimestamp": + queryMarketingBudgetWithTimestamp(dbClient); + break; + case "createtablewithtimestamp": + createTableWithTimestamp(dbAdminClient, database); + 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, database); + 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, backup, getVersionTime(dbClient)); + break; + case "cancelcreatebackup": + cancelCreateBackup( + dbAdminClient, + database, + BackupId.of(backup.getInstanceId(), backup.getBackup() + "_cancel")); + break; + case "listbackupoperations": + listBackupOperations(instanceAdminClient, database, backup); + break; + case "listdatabaseoperations": + listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId()); + break; + case "listbackups": + listBackups(instanceAdminClient, database, backup); + break; + case "restorebackup": + restoreBackup( + dbAdminClient, + backup, + database, + DatabaseId.of(database.getInstanceId(), createRestoredSampleDbId(database))); + break; + case "updatebackup": + updateBackup(dbAdminClient, backup); + break; + case "deletebackup": + deleteBackup(dbAdminClient, backup); + break; + default: + printUsageAndExit(); + } + } + + static Timestamp getVersionTime(DatabaseClient dbClient) { + // Generates a version time for the backup + Timestamp versionTime; + try (ResultSet resultSet = dbClient.singleUse() + .executeQuery(Statement.of("SELECT CURRENT_TIMESTAMP()"))) { + resultSet.next(); + versionTime = resultSet.getTimestamp(0); + } + return versionTime; + } + + 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(); + try { + 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 backupName = + String.format( + "%s_%02d", + db.getDatabase(), LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR)); + BackupId backup = BackupId.of(db.getInstanceId(), backupName); + if (args.length == 4) { + backupName = args[3]; + } + + // [START init_client] + DatabaseClient dbClient = spanner.getDatabaseClient(db); + DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + // Use client here... + // [END init_client] + + run(dbClient, dbAdminClient, instanceAdminClient, command, db, backup); + // [START init_client] + } finally { + spanner.close(); + } + // [END init_client] + System.out.println("Closed client"); + } +} diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseSample.java similarity index 58% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseSample.java index 958be6e20a..5fcd9ab5ed 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseSample.java @@ -14,55 +14,45 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_update_database] - import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerExceptionFactory; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.common.collect.Lists; -import com.google.protobuf.FieldMask; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.cloud.spanner.SpannerOptions; import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; -import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; -import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class UpdateDatabaseSample { - static void updateDatabase() throws IOException { + static void updateDatabase() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; final String databaseId = "my-database"; - updateDatabase(projectId, instanceId, databaseId); } - static void updateDatabase( - String projectId, String instanceId, String databaseId) throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - try { - final Database database = - Database.newBuilder() - .setName(DatabaseName.of(projectId, instanceId, databaseId).toString()) - .setEnableDropProtection(true).build(); - final UpdateDatabaseRequest updateDatabaseRequest = - UpdateDatabaseRequest.newBuilder() - .setDatabase(database) - .setUpdateMask( - FieldMask.newBuilder().addAllPaths( - Lists.newArrayList("enable_drop_protection")).build()) - .build(); + static void updateDatabase(String projectId, String instanceId, String databaseId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + + DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); + Database databaseToUpdate = + databaseAdminClient.newDatabaseBuilder(dbId).enableDropProtection().build(); OperationFuture operation = - databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest); - System.out.printf("Waiting for update operation for %s to complete...\n", databaseId); + databaseAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); + System.out.printf("Waiting for update operation for %s to complete...\n", dbId); Database updatedDb = operation.get(5, TimeUnit.MINUTES); - System.out.printf("Updated database %s.\n", updatedDb.getName()); + System.out.printf("Updated database %s.\n", updatedDb.getId().getName()); } catch (ExecutionException | TimeoutException e) { // If the operation failed during execution, expose the cause. throw SpannerExceptionFactory.asSpannerException(e.getCause()); diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSample.java similarity index 73% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSample.java index 5306358714..8971399093 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSample.java @@ -14,22 +14,23 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; //[START spanner_update_database_with_default_leader] import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; 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.DatabaseName; -import java.io.IOException; +import com.google.cloud.spanner.SpannerOptions; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.Collections; import java.util.concurrent.ExecutionException; public class UpdateDatabaseWithDefaultLeaderSample { - static void updateDatabaseWithDefaultLeader() throws IOException { + static void updateDatabaseWithDefaultLeader() { // TODO(developer): Replace these variables before running the sample. final String projectId = "my-project"; final String instanceId = "my-instance"; @@ -39,22 +40,27 @@ static void updateDatabaseWithDefaultLeader() throws IOException { } static void updateDatabaseWithDefaultLeader( - String projectId, String instanceId, String databaseId, String defaultLeader) - throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - try { - databaseAdminClient - .updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), + String projectId, String instanceId, String databaseId, String defaultLeader) { + try (Spanner spanner = SpannerOptions + .newBuilder() + .setProjectId(projectId) + .build() + .getService()) { + final DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient(); + final OperationFuture operation = databaseAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, Collections.singletonList( String.format( "ALTER DATABASE `%s` SET OPTIONS (default_leader = '%s')", databaseId, defaultLeader ) - ) - ).get(); + ), + null + ); + operation.get(); System.out.println("Updated default leader to " + defaultLeader); } catch (ExecutionException e) { // If the operation failed during execution, expose the cause. diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateInstanceConfigSample.java similarity index 50% rename from samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateInstanceConfigSample.java rename to samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateInstanceConfigSample.java index 1fb5cd0e6f..8110e3782c 100644 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/UpdateInstanceConfigSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/admin/archived/UpdateInstanceConfigSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,63 +14,54 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; // [START spanner_update_instance_config] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.InstanceConfigId; +import com.google.cloud.spanner.InstanceConfigInfo; +import com.google.cloud.spanner.InstanceConfigInfo.InstanceConfigField; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableList; -import com.google.protobuf.FieldMask; -import com.google.spanner.admin.instance.v1.InstanceConfig; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import com.google.spanner.admin.instance.v1.UpdateInstanceConfigRequest; -import java.io.IOException; +import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; class UpdateInstanceConfigSample { - - static void updateInstanceConfig() throws IOException { + static void updateInstanceConfig() { // TODO(developer): Replace these variables before running the sample. String projectId = "my-project"; String instanceConfigId = "custom-instance-config"; updateInstanceConfig(projectId, instanceConfigId); } - static void updateInstanceConfig(String projectId, String instanceConfigId) throws IOException { - try (final InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final InstanceConfigName instanceConfigName = - InstanceConfigName.of(projectId, instanceConfigId); - final InstanceConfig instanceConfig = - InstanceConfig.newBuilder() - .setName(instanceConfigName.toString()) + static void updateInstanceConfig(String projectId, String instanceConfigId) { + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + final InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); + InstanceConfigInfo instanceConfigInfo = + InstanceConfig.newBuilder(InstanceConfigId.of(projectId, instanceConfigId)) .setDisplayName("updated custom instance config") - .putLabels("updated", "true").build(); - /** - * The field mask must always be specified; this prevents any future - * fields in [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig] - * from being erased accidentally by clients that do not know about them. - */ - final UpdateInstanceConfigRequest updateInstanceConfigRequest = - UpdateInstanceConfigRequest.newBuilder() - .setInstanceConfig(instanceConfig) - .setUpdateMask( - FieldMask.newBuilder().addAllPaths(ImmutableList.of("display_name", "labels")) - .build()).build(); + .addLabel("updated", "true") + .build(); + final OperationFuture operation = + instanceAdminClient.updateInstanceConfig( + instanceConfigInfo, + ImmutableList.of(InstanceConfigField.DISPLAY_NAME, InstanceConfigField.LABELS)); try { - System.out.printf("Waiting for update operation on %s to complete...\n", - instanceConfigName); - InstanceConfig instanceConfigResult = - instanceAdminClient.updateInstanceConfigAsync( - updateInstanceConfigRequest).get(5, TimeUnit.MINUTES); + System.out.printf("Waiting for update operation on %s to complete...\n", instanceConfigId); + InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES); System.out.printf( "Updated instance configuration %s with new display name %s\n", - instanceConfigResult.getName(), instanceConfig.getDisplayName()); + instanceConfig.getId(), instanceConfig.getDisplayName()); } catch (ExecutionException | TimeoutException e) { System.out.printf( "Error: Updating instance config %s failed with error message %s\n", - instanceConfig.getName(), e.getMessage()); + instanceConfigInfo.getId(), e.getMessage()); e.printStackTrace(); } catch (InterruptedException e) { System.out.println( diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddAndDropDatabaseRole.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddAndDropDatabaseRole.java deleted file mode 100644 index d88fe72dc8..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/AddAndDropDatabaseRole.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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_add_and_drop_database_role] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.DatabaseName; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class AddAndDropDatabaseRole { - - static void addAndDropDatabaseRole() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - String databaseId = "my-database"; - String parentRole = "parent_role"; - String childRole = "child_role"; - addAndDropDatabaseRole(projectId, instanceId, databaseId, parentRole, childRole); - } - - static void addAndDropDatabaseRole( - String projectId, String instanceId, String databaseId, String parentRole, String childRole) - throws IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - try { - System.out.println("Waiting for role create operation to complete..."); - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of( - String.format("CREATE ROLE %s", parentRole), - String.format("GRANT SELECT ON TABLE Albums TO ROLE %s", parentRole), - String.format("CREATE ROLE %s", childRole), - String.format("GRANT ROLE %s TO ROLE %s", parentRole, childRole))) - .get(5, TimeUnit.MINUTES); - System.out.printf( - "Created roles %s and %s and granted privileges%n", parentRole, childRole); - // Delete role and membership. - System.out.println("Waiting for role revoke & drop operation to complete..."); - databaseAdminClient.updateDatabaseDdlAsync( - DatabaseName.of(projectId, instanceId, databaseId), - ImmutableList.of( - String.format("REVOKE ROLE %s FROM ROLE %s", parentRole, childRole), - String.format("DROP ROLE %s", childRole))).get(5, TimeUnit.MINUTES); - System.out.printf("Revoked privileges and dropped role %s%n", childRole); - } catch (ExecutionException | TimeoutException e) { - System.out.printf( - "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage()); - e.printStackTrace(); - } catch (InterruptedException e) { - System.out.println( - "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted"); - } - } -} -// [END spanner_add_and_drop_database_role] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSample.java deleted file mode 100644 index 8d26f1ced5..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSample.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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_version_retention_period] - -import com.google.cloud.spanner.SpannerException; -import com.google.cloud.spanner.SpannerExceptionFactory; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.common.collect.Lists; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.InstanceName; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -public class CreateDatabaseWithVersionRetentionPeriodSample { - - static void createDatabaseWithVersionRetentionPeriod() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - String databaseId = "my-database"; - String versionRetentionPeriod = "7d"; - - createDatabaseWithVersionRetentionPeriod(projectId, instanceId, databaseId, - versionRetentionPeriod); - } - - static void createDatabaseWithVersionRetentionPeriod(String projectId, - String instanceId, String databaseId, String versionRetentionPeriod) throws IOException { - DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - try { - CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setParent(InstanceName.of(projectId, instanceId).toString()) - .setCreateStatement("CREATE DATABASE `" + databaseId + "`") - .addAllExtraStatements(Lists.newArrayList("ALTER DATABASE " + "`" + databaseId + "`" - + " SET OPTIONS ( version_retention_period = '" + versionRetentionPeriod + "' )")) - .build(); - Database database = - databaseAdminClient.createDatabaseAsync(request).get(); - System.out.println("Created database [" + database.getName() + "]"); - System.out.println("\tVersion retention period: " + database.getVersionRetentionPeriod()); - System.out.println("\tEarliest version time: " + database.getEarliestVersionTime()); - } 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_with_version_retention_period] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceConfigSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceConfigSample.java deleted file mode 100644 index 49ce023b24..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceConfigSample.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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; - -// [START spanner_create_instance_config] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.CreateInstanceConfigRequest; -import com.google.spanner.admin.instance.v1.InstanceConfig; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import com.google.spanner.admin.instance.v1.ProjectName; -import com.google.spanner.admin.instance.v1.ReplicaInfo; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -class CreateInstanceConfigSample { - - static void createInstanceConfig() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String baseInstanceConfigId = "nam11"; - String instanceConfigId = "custom-instance-config4"; - - createInstanceConfig(projectId, baseInstanceConfigId, instanceConfigId); - } - - static void createInstanceConfig( - String projectId, String baseInstanceConfigId, String instanceConfigId) throws IOException { - try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final InstanceConfigName baseInstanceConfigName = InstanceConfigName.of(projectId, - baseInstanceConfigId); - final InstanceConfig baseConfig = - instanceAdminClient.getInstanceConfig(baseInstanceConfigName.toString()); - final InstanceConfigName instanceConfigName = InstanceConfigName.of(projectId, - instanceConfigId); - /** - * The replicas for the custom instance configuration must include all the replicas of the - * base configuration, in addition to at least one from the list of optional replicas of the - * base configuration. - */ - final List replicas = - Stream.concat(baseConfig.getReplicasList().stream(), - baseConfig.getOptionalReplicasList().stream().limit(1)).collect(Collectors.toList()); - final InstanceConfig instanceConfig = - InstanceConfig.newBuilder().setName(instanceConfigName.toString()) - .setBaseConfig(baseInstanceConfigName.toString()) - .setDisplayName("Instance Configuration").addAllReplicas(replicas).build(); - final CreateInstanceConfigRequest createInstanceConfigRequest = - CreateInstanceConfigRequest.newBuilder().setParent(ProjectName.of(projectId).toString()) - .setInstanceConfigId(instanceConfigId).setInstanceConfig(instanceConfig).build(); - try { - System.out.printf("Waiting for create operation for %s to complete...\n", - instanceConfigName); - InstanceConfig instanceConfigResult = - instanceAdminClient.createInstanceConfigAsync( - createInstanceConfigRequest).get(5, TimeUnit.MINUTES); - System.out.printf("Created instance configuration %s\n", instanceConfigResult.getName()); - } catch (ExecutionException | TimeoutException e) { - System.out.printf( - "Error: Creating instance configuration %s failed with error message %s\n", - instanceConfig.getName(), e.getMessage()); - } catch (InterruptedException e) { - System.out.println( - "Error: Waiting for createInstanceConfig operation to finish was interrupted"); - } - } - } -} -// [END spanner_create_instance_config] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigExample.java deleted file mode 100644 index aa785fe57d..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigExample.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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; - -// [START spanner_create_instance_with_autoscaling_config] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.AutoscalingConfig; -import com.google.spanner.admin.instance.v1.CreateInstanceRequest; -import com.google.spanner.admin.instance.v1.Instance; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import com.google.spanner.admin.instance.v1.ProjectName; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class CreateInstanceWithAutoscalingConfigExample { - - static void createInstance() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - createInstance(projectId, instanceId); - } - - static void createInstance(String projectId, String instanceId) throws IOException { - try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - // Set Instance configuration. - String configId = "regional-us-central1"; - String displayName = "Descriptive name"; - - // Create an autoscaling config. - // When autoscaling_config is enabled, node_count and processing_units fields - // need not be specified. - AutoscalingConfig autoscalingConfig = - AutoscalingConfig.newBuilder() - .setAutoscalingLimits( - AutoscalingConfig.AutoscalingLimits.newBuilder().setMinNodes(1).setMaxNodes(2)) - .setAutoscalingTargets( - AutoscalingConfig.AutoscalingTargets.newBuilder() - .setHighPriorityCpuUtilizationPercent(65) - .setStorageUtilizationPercent(95)) - .build(); - Instance instance = - Instance.newBuilder() - .setAutoscalingConfig(autoscalingConfig) - .setDisplayName(displayName) - .setConfig( - InstanceConfigName.of(projectId, configId).toString()) - .build(); - - // Creates a new instance - System.out.printf("Creating instance %s.%n", instanceId); - try { - // Wait for the createInstance operation to finish. - Instance instanceResult = instanceAdminClient.createInstanceAsync( - CreateInstanceRequest.newBuilder() - .setParent(ProjectName.of(projectId).toString()) - .setInstanceId(instanceId) - .setInstance(instance) - .build()).get(); - System.out.printf("Autoscaler instance %s was successfully created%n", - instanceResult.getName()); - } catch (ExecutionException e) { - System.out.printf( - "Error: Creating instance %s failed with error message %s%n", - instance.getName(), e.getMessage()); - } catch (InterruptedException e) { - System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); - } - } - } -} -// [END spanner_create_instance_with_autoscaling_config] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsExample.java deleted file mode 100644 index 9aa0487098..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsExample.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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; - -//[START spanner_create_instance_with_processing_units] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.spanner.admin.instance.v1.CreateInstanceRequest; -import com.google.spanner.admin.instance.v1.Instance; -import com.google.spanner.admin.instance.v1.InstanceConfigName; -import com.google.spanner.admin.instance.v1.ProjectName; -import java.io.IOException; - -class CreateInstanceWithProcessingUnitsExample { - - static void createInstance() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - createInstance(projectId, instanceId); - } - - static void createInstance(String projectId, String instanceId) throws IOException { - try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - // Set Instance configuration. - String configId = "regional-us-central1"; - // This will create an instance with the processing power of 0.2 nodes. - int processingUnits = 500; - String displayName = "Descriptive name"; - - try { - // Creates a new instance - System.out.printf("Creating instance %s.%n", instanceId); - Instance instance = - Instance.newBuilder() - .setDisplayName(displayName) - .setProcessingUnits(processingUnits) - .setConfig( - InstanceConfigName.of(projectId, configId).toString()) - .build(); - // Wait for the createInstance operation to finish. - System.out.printf("Waiting for operation on %s to complete...%n", instanceId); - Instance createdInstance = instanceAdminClient.createInstanceAsync( - CreateInstanceRequest.newBuilder() - .setParent(ProjectName.of(projectId).toString()) - .setInstanceId(instanceId) - .setInstance(instance) - .build()).get(); - - System.out.printf("Created instance %s.%n", createdInstance.getName()); - System.out.printf("Instance %s has %d processing units.%n", createdInstance.getName(), - createdInstance.getProcessingUnits()); - } catch (Exception e) { - System.out.printf("Error: %s.%n", e.getMessage()); - } - } - } -} -//[END spanner_create_instance_with_processing_units] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/EnableFineGrainedAccess.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/EnableFineGrainedAccess.java deleted file mode 100644 index 7f66992cad..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/EnableFineGrainedAccess.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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_enable_fine_grained_access] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.common.collect.ImmutableList; -import com.google.iam.v1.Binding; -import com.google.iam.v1.GetIamPolicyRequest; -import com.google.iam.v1.GetPolicyOptions; -import com.google.iam.v1.Policy; -import com.google.iam.v1.SetIamPolicyRequest; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.type.Expr; -import java.io.IOException; - -public class EnableFineGrainedAccess { - - static void enableFineGrainedAccess() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - String databaseId = "my-database"; - String iamMember = "user:alice@example.com"; - String role = "my-role"; - String title = "my-condition-title"; - enableFineGrainedAccess(projectId, instanceId, databaseId, iamMember, title, role); - } - - static void enableFineGrainedAccess( - String projectId, - String instanceId, - String databaseId, - String iamMember, - String title, - String role) throws IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - final GetPolicyOptions options = - GetPolicyOptions.newBuilder().setRequestedPolicyVersion(3).build(); - final GetIamPolicyRequest getRequest = - GetIamPolicyRequest.newBuilder() - .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString()) - .setOptions(options).build(); - final Policy policy = databaseAdminClient.getIamPolicy(getRequest); - int policyVersion = policy.getVersion(); - // The policy in the response from getDatabaseIAMPolicy might use the policy version - // that you specified, or it might use a lower policy version. For example, if you - // specify version 3, but the policy has no conditional role bindings, the response - // uses version 1. Valid values are 0, 1, and 3. - if (policy.getVersion() < 3) { - // conditional role bindings work with policy version 3 - policyVersion = 3; - } - - Binding binding1 = - Binding.newBuilder() - .setRole("roles/spanner.fineGrainedAccessUser") - .addAllMembers(ImmutableList.of(iamMember)) - .build(); - - Binding binding2 = - Binding.newBuilder() - .setRole("roles/spanner.databaseRoleUser") - .setCondition( - Expr.newBuilder().setDescription(title).setExpression( - String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role) - ).setTitle(title).build()) - .addAllMembers(ImmutableList.of(iamMember)) - .build(); - ImmutableList bindings = - ImmutableList.builder() - .addAll(policy.getBindingsList()) - .add(binding1) - .add(binding2) - .build(); - Policy policyWithConditions = - Policy.newBuilder() - .setVersion(policyVersion) - .setEtag(policy.getEtag()) - .addAllBindings(bindings) - .build(); - final SetIamPolicyRequest setRequest = - SetIamPolicyRequest.newBuilder() - .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString()) - .setPolicy(policyWithConditions).build(); - final Policy response = databaseAdminClient.setIamPolicy(setRequest); - System.out.printf( - "Enabled fine-grained access in IAM with version %d%n", response.getVersion()); - } -} -// [END spanner_enable_fine_grained_access] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabaseRoles.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabaseRoles.java deleted file mode 100644 index d5d8859673..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabaseRoles.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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_list_database_roles] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPage; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPagedResponse; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.spanner.admin.database.v1.DatabaseRole; -import java.io.IOException; - -public class ListDatabaseRoles { - - static void listDatabaseRoles() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - String instanceId = "my-instance"; - String databaseId = "my-database"; - listDatabaseRoles(projectId, instanceId, databaseId); - } - - static void listDatabaseRoles(String projectId, String instanceId, String databaseId) - throws IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - DatabaseName databaseName = DatabaseName.of(projectId, instanceId, databaseId); - ListDatabaseRolesPagedResponse response - = databaseAdminClient.listDatabaseRoles(databaseName); - System.out.println("List of Database roles"); - for (ListDatabaseRolesPage page : response.iteratePages()) { - for (DatabaseRole role : page.iterateAll()) { - System.out.printf("Obtained role %s%n", role.getName()); - } - } - } -} -// [END spanner_list_database_roles] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabasesSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabasesSample.java deleted file mode 100644 index 7518064f2c..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListDatabasesSample.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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_list_databases] - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabasesPage; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabasesPagedResponse; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.InstanceName; -import java.io.IOException; - -public class ListDatabasesSample { - - static void listDatabases() throws IOException { - // TODO(developer): Replace these variables before running the sample. - final String projectId = "my-project"; - final String instanceId = "my-instance"; - listDatabases(projectId, instanceId); - } - - static void listDatabases(String projectId, String instanceId) throws IOException { - final DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); - - ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId)); - - System.out.println("Databases for projects/" + projectId + "/instances/" + instanceId); - - for (ListDatabasesPage page : response.iteratePages()) { - for (Database database : page.iterateAll()) { - final String defaultLeader = database.getDefaultLeader().equals("") - ? "" : "(default leader = " + database.getDefaultLeader() + ")"; - System.out.println("\t" + database.getName() + " " + defaultLeader); - } - } - } -} -//[END spanner_list_databases] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigOperationsSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigOperationsSample.java deleted file mode 100644 index 6970c50069..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/admin/generated/ListInstanceConfigOperationsSample.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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; - -// [START spanner_list_instance_config_operations] - -import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.longrunning.Operation; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; -import com.google.spanner.admin.instance.v1.ListInstanceConfigOperationsRequest; -import com.google.spanner.admin.instance.v1.ProjectName; -import java.io.IOException; - -public class ListInstanceConfigOperationsSample { - - static void listInstanceConfigOperations() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project"; - listInstanceConfigOperations(projectId); - } - - static void listInstanceConfigOperations(String projectId) throws IOException { - try (final InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) { - final ProjectName projectName = ProjectName.of(projectId); - System.out.printf( - "Getting list of instance config operations for project %s...\n", - projectId); - final ListInstanceConfigOperationsRequest request = - ListInstanceConfigOperationsRequest.newBuilder() - .setParent(projectName.toString()) - .setFilter("(metadata.@type=type.googleapis.com/" - + "google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)").build(); - final Iterable instanceConfigOperations = - instanceAdminClient.listInstanceConfigOperations(request).iterateAll(); - for (Operation operation : instanceConfigOperations) { - CreateInstanceConfigMetadata metadata = - operation.getMetadata().unpack(CreateInstanceConfigMetadata.class); - System.out.printf( - "Create instance config operation for %s is %d%% completed.\n", - metadata.getInstanceConfig().getName(), metadata.getProgress().getProgressPercent()); - } - System.out.printf( - "Obtained list of instance config operations for project %s...\n", - projectName); - } catch (InvalidProtocolBufferException e) { - System.out.printf( - "Error: Listing instance config operations failed with error message %s\n", - e.getMessage()); - } - } -} -// [END spanner_list_instance_config_operations] diff --git a/samples/snippets/src/test/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSampleIT.java index f719424758..a22b5ab375 100644 --- a/samples/snippets/src/test/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/AlterTableWithForeignKeyDeleteCascadeSampleIT.java @@ -18,22 +18,24 @@ import static org.junit.Assert.assertTrue; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.InstanceName; import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class AlterTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBase { +public class AlterTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBaseV2 { @Test public void testAlterTableWithForeignKeyDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase( - instanceId, - databaseId, - Arrays.asList( + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .setParent(InstanceName.of(projectId, instanceId).toString()) + .addAllExtraStatements(Arrays.asList( "CREATE TABLE Customers (\n" + " CustomerId INT64 NOT NULL,\n" + " CustomerName STRING(62) NOT NULL,\n" @@ -45,15 +47,15 @@ public void testAlterTableWithForeignKeyDeleteCascade() throws Exception { + " CONSTRAINT FKShoppingCartsCustomerId" + " FOREIGN KEY (CustomerId)\n" + " REFERENCES Customers (CustomerId)\n" - + " ) PRIMARY KEY (CartId)\n")) - .get(5, TimeUnit.MINUTES); + + " ) PRIMARY KEY (CartId)\n")).build(); + databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> AlterTableWithForeignKeyDeleteCascadeSample.alterForeignKeyDeleteCascadeConstraint( - databaseAdminClient, instanceId, databaseId)); + projectId, instanceId, databaseId)); assertTrue( "Expected to have created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/CopyBackupIT.java b/samples/snippets/src/test/java/com/example/spanner/CopyBackupIT.java new file mode 100644 index 0000000000..29854011e1 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/CopyBackupIT.java @@ -0,0 +1,111 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +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/CreateDatabaseWithDefaultLeaderSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSampleIT.java index d56d4b14f8..fff67b38b8 100644 --- a/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSampleIT.java @@ -1,11 +1,11 @@ /* - * Copyright 2021 Google LLC + * Copyright 2023 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 + * 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, @@ -18,33 +18,34 @@ import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.InstanceConfigId; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceName; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; -public class CreateDatabaseWithDefaultLeaderSampleIT extends SampleTestBase { +@RunWith(JUnit4.class) +public class CreateDatabaseWithDefaultLeaderSampleIT extends SampleTestBaseV2 { @Test public void testCreateDatabaseWithDefaultLeader() throws Exception { final String databaseId = idGenerator.generateDatabaseId(); // Finds possible default leader - final InstanceConfigId instanceConfigId = instanceAdminClient - .getInstance(multiRegionalInstanceId) - .getInstanceConfigId(); - final InstanceConfig config = instanceAdminClient - .getInstanceConfig(instanceConfigId.getInstanceConfig()); + + final String instanceConfigId = instanceAdminClient.getInstance( + InstanceName.of(projectId, multiRegionalInstanceId)).getConfig(); + final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); assertTrue( "Expected instance config " + instanceConfigId + " to have at least one leader option", - config.getLeaderOptions().size() > 0 + config.getLeaderOptionsCount() > 0 ); - final String defaultLeader = config.getLeaderOptions().get(0); + final String defaultLeader = config.getLeaderOptions(0); // Runs sample final String out = SampleRunner.runSample(() -> CreateDatabaseWithDefaultLeaderSample.createDatabaseWithDefaultLeader( - projectId, - multiRegionalInstanceId, + getInstanceName(projectId, multiRegionalInstanceId), databaseId, defaultLeader ) diff --git a/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSampleIT.java index 0e77ffc9a2..98832f1f51 100644 --- a/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CreateDatabaseWithVersionRetentionPeriodSampleIT.java @@ -23,10 +23,11 @@ import org.junit.runners.JUnit4; /** - * Integration tests for {@link CreateDatabaseWithVersionRetentionPeriodSample} + * Integration tests for + * {@link com.example.spanner.admin.archived.CreateDatabaseWithVersionRetentionPeriodSample} */ @RunWith(JUnit4.class) -public class CreateDatabaseWithVersionRetentionPeriodSampleIT extends SampleTestBase { +public class CreateDatabaseWithVersionRetentionPeriodSampleIT extends SampleTestBaseV2 { @Test public void createsDatabaseWithVersionRetentionPeriod() throws Exception { @@ -35,7 +36,7 @@ public void createsDatabaseWithVersionRetentionPeriod() throws Exception { final String out = SampleRunner.runSample(() -> CreateDatabaseWithVersionRetentionPeriodSample .createDatabaseWithVersionRetentionPeriod( - databaseAdminClient, instanceId, databaseId, versionRetentionPeriod + projectId, instanceId, databaseId, versionRetentionPeriod )); assertThat(out).contains( diff --git a/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithAutoscalingConfigSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithAutoscalingConfigSampleIT.java index 76eef2529d..041d6b97ac 100644 --- a/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithAutoscalingConfigSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithAutoscalingConfigSampleIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * 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. @@ -18,25 +18,19 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.cloud.spanner.InstanceId; -import java.util.UUID; +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; -public class CreateInstanceWithAutoscalingConfigSampleIT extends SampleTestBase { +public class CreateInstanceWithAutoscalingConfigSampleIT extends SampleTestBaseV2 { @Test public void testCreateInstanceWithAutoscalingConfig() throws Exception { - String instanceId = String.format("autoscaler-%s", UUID.randomUUID()); + String instanceId = idGenerator.generateInstanceId(); String out = SampleRunner.runSample( - () -> { - try { - CreateInstanceWithAutoscalingConfigExample.createInstance(projectId, instanceId); - } finally { - spanner.getInstanceAdminClient().deleteInstance(instanceId); - } - }); + () -> CreateInstanceWithAutoscalingConfigExample.createInstance(projectId, instanceId)); assertThat(out) - .contains(String.format("Autoscaler instance %s", InstanceId.of(projectId, instanceId))); + .contains(String.format("Autoscaler instance %s", + InstanceName.of(projectId, instanceId).toString())); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithProcessingUnitsSampleIT.java similarity index 93% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithProcessingUnitsSampleIT.java index 13778dff82..dcddea4929 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithProcessingUnitsSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CreateInstanceWithProcessingUnitsSampleIT.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner; import static com.google.common.truth.Truth.assertThat; -import com.example.spanner.SampleRunner; import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; diff --git a/samples/snippets/src/test/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSampleIT.java index b2dd6638ce..481bdc16bd 100644 --- a/samples/snippets/src/test/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CreateTableWithForeignKeyDeleteCascadeSampleIT.java @@ -18,28 +18,30 @@ import static org.junit.Assert.assertTrue; -import java.util.Arrays; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.InstanceName; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class CreateTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBase { +public class CreateTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBaseV2 { @Test public void testCreateTableWithForeignKeyDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase(instanceId, databaseId, Arrays.asList()) - .get(5, TimeUnit.MINUTES); + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .setParent(InstanceName.of(projectId, instanceId).toString()).build(); + databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> CreateTableWithForeignKeyDeleteCascadeSample - .createForeignKeyDeleteCascadeConstraint( - databaseAdminClient, instanceId, databaseId)); + .createForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId)); assertTrue( "Expected to have created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/CustomInstanceConfigSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/CustomInstanceConfigSampleIT.java index ba1eef6850..354e11a504 100644 --- a/samples/snippets/src/test/java/com/example/spanner/CustomInstanceConfigSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/CustomInstanceConfigSampleIT.java @@ -1,11 +1,11 @@ /* - * Copyright 2022 Google LLC + * 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 + * 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, @@ -19,8 +19,11 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; -public class CustomInstanceConfigSampleIT extends SampleTestBase { +@RunWith(JUnit4.class) +public class CustomInstanceConfigSampleIT extends SampleTestBaseV2 { @Test public void testCustomInstanceConfigOperations() throws Exception { @@ -34,26 +37,37 @@ public void testCustomInstanceConfigOperations() throws Exception { projectId, instanceConfigName, customInstanceConfigId)); assertTrue(out1.contains("Created instance configuration")); - // List the instance config operations. + // Fetch the instance config that was created above. final String out2 = + SampleRunner.runSample( + () -> GetInstanceConfigSample.getInstanceConfig(projectId, instanceConfigName)); + assertTrue(out2.contains("Available leader options for instance config")); + + // Fetch the instance config that was created above. + final String out3 = + SampleRunner.runSample( + () -> ListInstanceConfigsSample.listInstanceConfigs(projectId)); + assertTrue(out3.contains("Available leader options for instance config")); + + // List the instance config operations. + final String out4 = SampleRunner.runSample( () -> ListInstanceConfigOperationsSample.listInstanceConfigOperations(projectId)); - assertTrue(out2.contains("List instance config operation")); + assertTrue(out4.contains("Obtained list of instance config operations")); // Update display name to a randomly generated instance config id. - final String out3 = + final String out5 = SampleRunner.runSample( () -> - UpdateInstanceConfigSample.updateInstanceConfig( - projectId, customInstanceConfigId)); - assertTrue(out3.contains("Updated instance configuration")); + UpdateInstanceConfigSample.updateInstanceConfig(projectId, customInstanceConfigId)); + assertTrue(out5.contains("Updated instance configuration")); // Delete the created instance config. - final String out4 = + final String out6 = SampleRunner.runSample( () -> DeleteInstanceConfigSample.deleteInstanceConfig(projectId, customInstanceConfigId)); - assertTrue(out4.contains("Deleted instance configuration")); + assertTrue(out6.contains("Deleted instance configuration")); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/DatabaseRolesIT.java b/samples/snippets/src/test/java/com/example/spanner/DatabaseRolesIT.java index b0a506430a..6c2b41c367 100644 --- a/samples/snippets/src/test/java/com/example/spanner/DatabaseRolesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/DatabaseRolesIT.java @@ -22,7 +22,8 @@ import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; @@ -33,20 +34,24 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Integration tests for FGAC samples for GoogleStandardSql dialect. */ +/** + * Integration tests for FGAC samples for GoogleStandardSql dialect. + */ @RunWith(JUnit4.class) -public class DatabaseRolesIT extends SampleTestBase { +public class DatabaseRolesIT extends SampleTestBaseV2 { private static DatabaseId databaseId; @BeforeClass public static void createTestDatabase() throws Exception { final String database = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase( - instanceId, - database, - ImmutableList.of( + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setParent( + com.google.spanner.admin.database.v1.InstanceName.of(projectId, instanceId) + .toString()) + .setCreateStatement("CREATE DATABASE `" + database + "`") + .addAllExtraStatements(Lists.newArrayList( "CREATE TABLE Singers (" + " SingerId INT64 NOT NULL," + " FirstName STRING(1024)," @@ -61,8 +66,8 @@ public static void createTestDatabase() throws Exception { + " AlbumTitle STRING(MAX)," + " MarketingBudget INT64" + ") PRIMARY KEY (SingerId, AlbumId)," - + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")) - .get(10, TimeUnit.MINUTES); + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build(); + databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); databaseId = DatabaseId.of(projectId, instanceId, database); } @@ -103,7 +108,7 @@ public void testAddAndDropDatabaseRole() throws Exception { SampleRunner.runSample( () -> AddAndDropDatabaseRole.addAndDropDatabaseRole( - projectId, instanceId, databaseId.getDatabase(), "new-parent", "new-child")); + projectId, instanceId, databaseId.getDatabase(), "new_parent", "new_child")); assertTrue(out.contains("Created roles new_parent and new_child and granted privileges")); assertTrue(out.contains("Revoked privileges and dropped role new_child")); } @@ -115,7 +120,7 @@ public void testListDatabaseRoles() throws Exception { () -> ListDatabaseRoles.listDatabaseRoles( projectId, instanceId, databaseId.getDatabase())); - assertTrue(out.contains("new_parent")); + assertTrue(out.contains("Obtained role ")); } @Test diff --git a/samples/snippets/src/test/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSampleIT.java index 1c58daded1..2e763949f4 100644 --- a/samples/snippets/src/test/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/DropForeignKeyConstraintDeleteCascadeSampleIT.java @@ -18,22 +18,24 @@ import static org.junit.Assert.assertTrue; -import java.util.Arrays; +import com.google.common.collect.Lists; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.InstanceName; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class DropForeignKeyConstraintDeleteCascadeSampleIT extends SampleTestBase { +public class DropForeignKeyConstraintDeleteCascadeSampleIT extends SampleTestBaseV2 { @Test public void testDropForeignKeyConstraintDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase( - instanceId, - databaseId, - Arrays.asList( + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .setParent(InstanceName.of(projectId, instanceId).toString()) + .addAllExtraStatements(Lists.newArrayList( "CREATE TABLE Customers (\n" + " CustomerId INT64 NOT NULL,\n" + " CustomerName STRING(62) NOT NULL,\n" @@ -45,15 +47,15 @@ public void testDropForeignKeyConstraintDeleteCascade() throws Exception { + " CONSTRAINT FKShoppingCartsCustomerName" + " FOREIGN KEY (CustomerName)\n" + " REFERENCES Customers (CustomerName) ON DELETE CASCADE\n" - + " ) PRIMARY KEY (CartId)\n")) - .get(5, TimeUnit.MINUTES); + + " ) PRIMARY KEY (CartId)\n")).build(); + databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> DropForeignKeyConstraintDeleteCascadeSample.deleteForeignKeyDeleteCascadeConstraint( - databaseAdminClient, instanceId, databaseId)); + projectId, instanceId, databaseId)); assertTrue( "Expected to have dropped foreign-key constraints from tables in created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/EncryptionKeyIT.java b/samples/snippets/src/test/java/com/example/spanner/EncryptionKeyIT.java index 03aa10a55f..2470540914 100644 --- a/samples/snippets/src/test/java/com/example/spanner/EncryptionKeyIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/EncryptionKeyIT.java @@ -18,11 +18,14 @@ import static com.google.common.truth.Truth.assertThat; +import com.example.spanner.admin.archived.CreateDatabaseWithEncryptionKey; 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; @@ -32,12 +35,13 @@ import org.junit.runners.JUnit4; /** - * Integration tests for: {@link CreateDatabaseWithEncryptionKey}, {@link - * CreateBackupWithEncryptionKey} and {@link RestoreBackupWithEncryptionKey} + * Integration tests for: {@link CreateDatabaseWithEncryptionKey}, + * {@link com.example.spanner.admin.archived.CreateBackupWithEncryptionKey} and + * {@link com.example.spanner.admin.archived.RestoreBackupWithEncryptionKey} */ @RunWith(JUnit4.class) @Ignore -public class EncryptionKeyIT extends SampleTestBase { +public class EncryptionKeyIT extends SampleTestBaseV2 { private static String key; @@ -58,16 +62,10 @@ public void testEncryptedDatabaseAndBackupAndRestore() throws Exception { final String restoreId = idGenerator.generateDatabaseId(); String out = SampleRunner.runSample(() -> - CreateDatabaseWithEncryptionKey.createDatabaseWithEncryptionKey( - databaseAdminClient, - projectId, - instanceId, - databaseId, - key - )); - assertThat(out).contains( - "Database projects/" + projectId + "/instances/" + instanceId + "/databases/" + databaseId - + " created with encryption key " + key); + 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( diff --git a/samples/snippets/src/test/java/com/example/spanner/GetDatabaseDdlSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/GetDatabaseDdlSampleIT.java index a345aa7156..a50f55e355 100644 --- a/samples/snippets/src/test/java/com/example/spanner/GetDatabaseDdlSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/GetDatabaseDdlSampleIT.java @@ -18,42 +18,44 @@ import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.InstanceConfigId; -import java.util.Arrays; +import com.google.common.collect.Lists; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceName; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class GetDatabaseDdlSampleIT extends SampleTestBase { +public class GetDatabaseDdlSampleIT extends SampleTestBaseV2 { @Test public void testGetDatabaseDdl() throws Exception { // Finds a possible new leader option - final InstanceConfigId instanceConfigId = instanceAdminClient - .getInstance(multiRegionalInstanceId) - .getInstanceConfigId(); - final InstanceConfig config = instanceAdminClient - .getInstanceConfig(instanceConfigId.getInstanceConfig()); + final String instanceConfigId = instanceAdminClient.getInstance( + InstanceName.of(projectId, multiRegionalInstanceId)).getConfig(); + final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); assertTrue( "Expected instance config " + instanceConfigId + " to have at least one leader option", - config.getLeaderOptions().size() > 0 + config.getLeaderOptionsList().size() > 0 ); - final String defaultLeader = config.getLeaderOptions().get(0); + final String defaultLeader = config.getLeaderOptions(0); // Creates database final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient.createDatabase( - multiRegionalInstanceId, - databaseId, - Arrays.asList( - "CREATE TABLE Singers (Id INT64 NOT NULL) PRIMARY KEY (Id)", - "ALTER DATABASE `" - + databaseId - + "` SET OPTIONS ( default_leader = '" - + defaultLeader - + "')" - ) - ).get(5, TimeUnit.MINUTES); + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setParent( + com.google.spanner.admin.database.v1.InstanceName.of(projectId, + multiRegionalInstanceId).toString()) + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .addAllExtraStatements(Lists.newArrayList( + "CREATE TABLE Singers (Id INT64 NOT NULL) PRIMARY KEY (Id)", + "ALTER DATABASE `" + + databaseId + + "` SET OPTIONS ( default_leader = '" + + defaultLeader + + "')" + )).build(); + databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample(() -> GetDatabaseDdlSample diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/ListDatabasesIT.java b/samples/snippets/src/test/java/com/example/spanner/ListDatabasesIT.java similarity index 92% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/ListDatabasesIT.java rename to samples/snippets/src/test/java/com/example/spanner/ListDatabasesIT.java index 730205b8b4..ce3e56bc64 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/ListDatabasesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/ListDatabasesIT.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner; import static org.junit.Assert.assertTrue; -import com.example.spanner.SampleRunner; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/samples/snippets/src/test/java/com/example/spanner/PgCaseSensitivitySampleIT.java b/samples/snippets/src/test/java/com/example/spanner/PgCaseSensitivitySampleIT.java index 1c2bca587c..fca4ac55c0 100644 --- a/samples/snippets/src/test/java/com/example/spanner/PgCaseSensitivitySampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/PgCaseSensitivitySampleIT.java @@ -18,24 +18,22 @@ import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Dialect; -import java.util.Collections; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.DatabaseDialect; +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; -public class PgCaseSensitivitySampleIT extends SampleTestBase { +public class PgCaseSensitivitySampleIT extends SampleTestBaseV2 { @Test public void testPgCaseSensitivitySample() throws Exception { final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase( - databaseAdminClient - .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) - .setDialect(Dialect.POSTGRESQL) - .build(), - Collections.emptyList()) - .get(); + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setCreateStatement(getCreateDatabaseStatement(databaseId, DatabaseDialect.POSTGRESQL)) + .setParent(InstanceName.of(projectId, instanceId).toString()) + .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build(); + databaseAdminClient.createDatabaseAsync(request).get(); final String out = SampleRunner.runSample( diff --git a/samples/snippets/src/test/java/com/example/spanner/PgInterleavedTableSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/PgInterleavedTableSampleIT.java index 6678f2a471..412147da0b 100644 --- a/samples/snippets/src/test/java/com/example/spanner/PgInterleavedTableSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/PgInterleavedTableSampleIT.java @@ -18,24 +18,22 @@ import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Dialect; -import java.util.Collections; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.DatabaseDialect; +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; -public class PgInterleavedTableSampleIT extends SampleTestBase { +public class PgInterleavedTableSampleIT extends SampleTestBaseV2 { @Test public void testPgInterleavedTableSample() throws Exception { final String databaseId = idGenerator.generateDatabaseId(); - databaseAdminClient - .createDatabase( - databaseAdminClient - .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) - .setDialect(Dialect.POSTGRESQL) - .build(), - Collections.emptyList()) - .get(); + final CreateDatabaseRequest request = + CreateDatabaseRequest.newBuilder() + .setCreateStatement(getCreateDatabaseStatement(databaseId, DatabaseDialect.POSTGRESQL)) + .setParent(InstanceName.of(projectId, instanceId).toString()) + .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build(); + databaseAdminClient.createDatabaseAsync(request).get(); final String out = SampleRunner.runSample( diff --git a/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java index 796df092c8..d55af6203d 100644 --- a/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java @@ -19,17 +19,18 @@ import static com.google.common.truth.Truth.assertThat; import com.google.cloud.Timestamp; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; 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.UUID; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,26 +41,23 @@ */ @RunWith(JUnit4.class) @SuppressWarnings("checkstyle:abbreviationaswordinname") -public class PgSpannerSampleIT { +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"); - private static final String databaseId = formatForTest(baseDbId); - private static final String encryptedDatabaseId = formatForTest(baseDbId); - private static final String encryptedBackupId = formatForTest(baseDbId); - private static final String encryptedRestoreId = formatForTest(baseDbId); static Spanner spanner; static DatabaseId dbId; static DatabaseAdminClient dbClient; @BeforeClass - public static void setUp() { + public static void setUp() throws IOException { SpannerOptions options = SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build(); spanner = options.getService(); - dbClient = spanner.getDatabaseAdminClient(); - dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId); + 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(); } @@ -68,34 +66,25 @@ static void deleteStaleTestDatabases() { Timestamp now = Timestamp.now(); Pattern samplePattern = getTestDbIdPattern(PgSpannerSampleIT.baseDbId); Pattern restoredPattern = getTestDbIdPattern("restored"); - for (Database db : dbClient.listDatabases(PgSpannerSampleIT.instanceId).iterateAll()) { + 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 (db.getId().getDatabase().length() >= DBID_LENGTH) { - if (samplePattern.matcher(toComparableId(PgSpannerSampleIT.baseDbId, - db.getId().getDatabase())).matches()) { - db.drop(); + if (databaseName.getDatabase().length() >= DBID_LENGTH) { + if (samplePattern.matcher( + toComparableId(PgSpannerSampleIT.baseDbId, databaseName.getDatabase())).matches()) { + dbClient.dropDatabase(db.getName()); } - if (restoredPattern.matcher(toComparableId("restored", db.getId().getDatabase())) + if (restoredPattern.matcher(toComparableId("restored", databaseName.getDatabase())) .matches()) { - db.drop(); + dbClient.dropDatabase(db.getName()); } } } } } - @AfterClass - public static void tearDown() { - dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); - dbClient.dropDatabase( - dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); - dbClient.dropDatabase(instanceId, encryptedDatabaseId); - dbClient.dropDatabase(instanceId, encryptedRestoreId); - dbClient.deleteBackup(instanceId, encryptedBackupId); - spanner.close(); - } - private static String toComparableId(String baseId, String existingId) { String zeroUuid = "00000000-0000-0000-0000-0000-00000000"; int shouldBeLength = (baseId + "-" + zeroUuid).length(); @@ -109,17 +98,13 @@ private static Pattern getTestDbIdPattern(String baseDbId) { Pattern.CASE_INSENSITIVE); } - static String formatForTest(String name) { - return name + "-" + UUID.randomUUID().toString().substring(0, DBID_LENGTH); - } - - private String runSample(String command) { + 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 + ":" + databaseId); - PgSpannerSample.main(new String[]{command, instanceId, databaseId}); + System.out.println(instanceId + ":" + dbId.getDatabase()); + PgSpannerSample.main(new String[]{command, instanceId, dbId.getDatabase()}); System.setOut(stdOut); return bout.toString(); } @@ -127,7 +112,7 @@ private String runSample(String command) { @Test public void testSample() throws Exception { assertThat(instanceId).isNotNull(); - assertThat(databaseId).isNotNull(); + assertThat(dbId.getDatabase()).isNotNull(); System.out.println("Create Database ..."); String out = runSample("createpgdatabase"); diff --git a/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java index d8e5b0c9c5..c906006ef5 100644 --- a/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java @@ -16,6 +16,7 @@ package com.example.spanner; +import static com.example.spanner.SpannerSampleIT.formatForTest; import static com.google.common.truth.Truth.assertThat; import com.google.api.gax.longrunning.OperationFuture; @@ -52,7 +53,7 @@ public class PgSpannerStandaloneExamplesIT { // The instance needs to exist for tests to pass. private static String instanceId = System.getProperty("spanner.test.instance"); private static String baseDatabaseId = System.getProperty("spanner.sample.database", "mysample"); - private static String databaseId = SpannerSampleIT.formatForTest(baseDatabaseId); + private static String databaseId = formatForTest(baseDatabaseId); private static DatabaseId dbId; private static DatabaseAdminClient dbClient; private static Spanner spanner; @@ -157,7 +158,7 @@ public void addNumericColumn_shouldSuccessfullyAddColumn() () -> { try { AddNumericColumnSample.addNumericColumn( - spanner.getDatabaseAdminClient(), instanceId, databaseId); + spanner.getOptions().getProjectId(), instanceId, databaseId); } catch (ExecutionException e) { System.out.printf( "Adding column `Revenue` failed: %s%n", e.getCause().getMessage()); @@ -226,7 +227,7 @@ public void addJsonbColumn_shouldSuccessfullyAddColumn() () -> { try { AddJsonbColumnSample.addJsonbColumn( - spanner.getDatabaseAdminClient(), instanceId, databaseId); + spanner.getOptions().getProjectId(), instanceId, databaseId); } catch (ExecutionException e) { System.out.printf( "Adding column `VenueDetails` failed: %s%n", e.getCause().getMessage()); diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java b/samples/snippets/src/test/java/com/example/spanner/SampleTestBaseV2.java similarity index 98% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java rename to samples/snippets/src/test/java/com/example/spanner/SampleTestBaseV2.java index e5ff002110..909e79738b 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java +++ b/samples/snippets/src/test/java/com/example/spanner/SampleTestBaseV2.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner; import com.example.spanner.SampleIdGenerator; import com.google.cloud.spanner.Spanner; @@ -113,6 +113,7 @@ public static void afterClass() throws InterruptedException { } } for (String backupId : idGenerator.getBackupIds()) { + System.out.println("Trying to delete " + backupId); try { // If the backup is not found, it is ignored (no exception is thrown) databaseAdminClient.deleteBackup( @@ -125,6 +126,7 @@ public static void afterClass() throws InterruptedException { } } for (String configId : idGenerator.getInstanceConfigIds()) { + System.out.println("Trying to delete " + configId); try { // If the config is not found, it is ignored (no exception is thrown) instanceAdminClient.deleteInstanceConfig(getInstanceConfigName(projectId, configId)); diff --git a/samples/snippets/src/test/java/com/example/spanner/SequenceSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/SequenceSampleIT.java index c8174ff4ca..de2c3961c2 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SequenceSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/SequenceSampleIT.java @@ -20,10 +20,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.Dialect; import com.google.common.collect.ImmutableList; -import java.util.Collections; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.DatabaseDialect; import java.util.HashSet; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -38,23 +37,23 @@ * dialects. */ @RunWith(Parameterized.class) -public class SequenceSampleIT extends SampleTestBase { +public class SequenceSampleIT extends SampleTestBaseV2 { - private static DatabaseId databaseId; + private static String databaseId; /** * Set of dialects for which database has already been created in this test suite. This helps in * limiting the number of databases created per dialect to one. */ - private static final HashSet dbInitializedDialects = new HashSet<>(); + private static final HashSet dbInitializedDialects = new HashSet<>(); @Parameters(name = "dialect = {0}") - public static Iterable data() { - return ImmutableList.of(Dialect.GOOGLE_STANDARD_SQL, Dialect.POSTGRESQL); + public static Iterable data() { + return ImmutableList.of(DatabaseDialect.GOOGLE_STANDARD_SQL, DatabaseDialect.POSTGRESQL); } @Parameter(0) - public static Dialect dialect; + public static DatabaseDialect dialect; @Before public void createTestDatabase() throws Exception { @@ -63,37 +62,36 @@ public void createTestDatabase() throws Exception { return; } dbInitializedDialects.add(dialect); - final String database = idGenerator.generateDatabaseId(); + databaseId = idGenerator.generateDatabaseId(); + CreateDatabaseRequest createDatabaseRequest = + CreateDatabaseRequest.newBuilder() + .setParent(getInstanceName(projectId, instanceId)) + .setCreateStatement(getCreateDatabaseStatement(databaseId, dialect)) + .setDatabaseDialect(dialect).build(); databaseAdminClient - .createDatabase( - databaseAdminClient - .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, database)) - .setDialect(dialect) - .build(), - Collections.emptyList()) + .createDatabaseAsync(createDatabaseRequest) .get(10, TimeUnit.MINUTES); - databaseId = DatabaseId.of(projectId, instanceId, database); } @Test public void createSequence() throws Exception { String out; - if (dialect == Dialect.GOOGLE_STANDARD_SQL) { + if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> CreateSequenceSample.createSequence( - projectId, instanceId, databaseId.getDatabase())); + projectId, instanceId, databaseId)); } else { out = runSample( () -> PgCreateSequenceSample.pgCreateSequence( - projectId, instanceId, databaseId.getDatabase())); + projectId, instanceId, databaseId)); } assertTrue( out.contains( - "Created Seq sequence and Customers table, where its key column " + "Created Seq sequence and Customers table, where the key column " + "CustomerId uses the sequence as a default value")); assertEquals(out.split("Inserted customer record with CustomerId", -1).length - 1, 3); assertTrue(out.contains("Number of customer records inserted is: 3")); @@ -102,18 +100,18 @@ public void createSequence() throws Exception { @Test public void alterSequence() throws Exception { String out; - if (dialect == Dialect.GOOGLE_STANDARD_SQL) { + if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> AlterSequenceSample.alterSequence( - projectId, instanceId, databaseId.getDatabase())); + projectId, instanceId, databaseId)); } else { out = runSample( () -> PgAlterSequenceSample.pgAlterSequence( - projectId, instanceId, databaseId.getDatabase())); + projectId, instanceId, databaseId)); } assertTrue( out.contains("Altered Seq sequence to skip an inclusive range between 1000 and 5000000")); @@ -124,17 +122,17 @@ public void alterSequence() throws Exception { @Test public void dropSequence() throws Exception { String out; - if (dialect == Dialect.GOOGLE_STANDARD_SQL) { + if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> - DropSequenceSample.dropSequence(projectId, instanceId, databaseId.getDatabase())); + DropSequenceSample.dropSequence(projectId, instanceId, databaseId)); } else { out = runSample( () -> PgDropSequenceSample.pgDropSequence( - projectId, instanceId, databaseId.getDatabase())); + projectId, instanceId, databaseId)); } assertTrue( out.contains( diff --git a/samples/snippets/src/test/java/com/example/spanner/SpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/SpannerSampleIT.java index 72e8c36d76..422d7618de 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SpannerSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/SpannerSampleIT.java @@ -20,10 +20,6 @@ import static org.junit.Assert.assertTrue; import com.google.cloud.Timestamp; -import com.google.cloud.spanner.Backup; -import com.google.cloud.spanner.BackupId; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Instance; @@ -36,26 +32,35 @@ 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; -import org.threeten.bp.LocalDate; -import org.threeten.bp.temporal.ChronoField; -/** Unit tests for {@code SpannerSample} */ +/** + * Unit tests for {@code SpannerSample} + */ @RunWith(JUnit4.class) @SuppressWarnings("checkstyle:abbreviationaswordinname") -public class SpannerSampleIT { +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"); @@ -66,24 +71,30 @@ public class SpannerSampleIT { 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 databaseId = formatForTest(baseDbId); - private static final String encryptedDatabaseId = formatForTest(baseDbId); private static final String encryptedBackupId = formatForTest(baseDbId); - private static final String encryptedRestoreId = formatForTest(baseDbId); private static final long STALE_INSTANCE_THRESHOLD_SECS = TimeUnit.SECONDS.convert(24L, TimeUnit.HOURS); static Spanner spanner; - static DatabaseId dbId; - static DatabaseAdminClient dbClient; + static DatabaseAdminClient databaseAdminClient; private static String key; private long lastUpdateDataTimeInMillis; - private String runSample(String command) throws Exception { + 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}); + 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(); } @@ -93,10 +104,9 @@ public static void setUp() throws Exception { SpannerOptions options = SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build(); spanner = options.getService(); - dbClient = spanner.getDatabaseAdminClient(); - dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId); + databaseAdminClient = DatabaseAdminClient.create(); // Delete stale test databases that have been created earlier by this test, but not deleted. - deleteStaleTestDatabases(instanceId, baseDbId); + deleteStaleTestDatabases(); key = String.format( "projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", @@ -132,20 +142,25 @@ private static void deleteStaleEncryptedTestInstances() throws InterruptedExcept } } - static void deleteStaleTestDatabases(String instanceId, String baseDbId) { + static void deleteStaleTestDatabases() throws IOException { Timestamp now = Timestamp.now(); - Pattern samplePattern = getTestDbIdPattern(baseDbId); + Pattern samplePattern = getTestDbIdPattern(SpannerSampleIT.baseDbId); Pattern restoredPattern = getTestDbIdPattern("restored"); - for (Database db : dbClient.listDatabases(instanceId).iterateAll()) { - if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(), - TimeUnit.SECONDS) > 24) { - if (db.getId().getDatabase().length() >= DBID_LENGTH) { - if (samplePattern.matcher(toComparableId(baseDbId, db.getId().getDatabase())).matches()) { - db.drop(); - } - if (restoredPattern.matcher(toComparableId("restored", db.getId().getDatabase())) - .matches()) { - db.drop(); + 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()); + } } } } @@ -153,305 +168,355 @@ static void deleteStaleTestDatabases(String instanceId, String baseDbId) { } @AfterClass - public static void tearDown() throws Exception { - dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); - dbClient.dropDatabase( - dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); - dbClient.dropDatabase(instanceId, encryptedDatabaseId); - dbClient.dropDatabase(instanceId, encryptedRestoreId); - dbClient.deleteBackup(instanceId, encryptedBackupId); + 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"); + String out = runSample("createdatabase", databaseId); assertThat(out).contains("Created database"); assertThat(out).contains(dbId.getName()); - runSample("write"); + System.out.println("Write data to sample tables ..."); + runSample("write", databaseId); - out = runSample("delete"); + System.out.println("Delete data to sample tables ..."); + out = runSample("delete", databaseId); assertThat(out).contains("Records deleted."); - runSample("write"); + runSample("write", databaseId); - out = runSample("read"); + System.out.println("Read data from sample tables ..."); + out = runSample("read", databaseId); assertThat(out).contains("1 1 Total Junk"); - out = runSample("query"); + out = runSample("query", databaseId); assertThat(out).contains("1 1 Total Junk"); - runSample("addmarketingbudget"); + 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"); - out = runSample("readstaledata"); + runSample("update", databaseId); + + System.out.println("Read stale data from sample tables ..."); + out = runSample("readstaledata", databaseId); assertThat(out).contains("1 1 NULL"); - runSample("writetransaction"); - out = runSample("querymarketingbudget"); + 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"); - runSample("addindex"); - out = runSample("queryindex"); + 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"); - out = runSample("readindex"); + 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"); - runSample("addstoringindex"); - out = runSample("readstoringindex"); + System.out.println("Add Storing index ..."); + runSample("addstoringindex", databaseId); + out = runSample("readstoringindex", databaseId); assertThat(out).contains("300000"); - out = runSample("readonlytransaction"); + System.out.println("Read storing index ..."); + out = runSample("readonlytransaction", databaseId); assertThat(out.replaceAll("[\r\n]+", " ")).containsMatch("(Total Junk.*){2}"); - out = runSample("addcommittimestamp"); + out = runSample("addcommittimestamp", databaseId); assertThat(out).contains("Added LastUpdateTime as a commit timestamp column"); - runSample("updatewithtimestamp"); - out = runSample("querywithtimestamp"); + runSample("updatewithtimestamp", databaseId); + out = runSample("querywithtimestamp", databaseId); assertThat(out).contains("1 1 1000000"); assertThat(out).contains("2 2 750000"); - out = runSample("createtablewithtimestamp"); + out = runSample("createtablewithtimestamp", databaseId); assertThat(out).contains("Created Performances table in database"); - runSample("writewithtimestamp"); - out = runSample("queryperformancestable"); + 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"); - out = runSample("querywithstruct"); + runSample("writestructdata", databaseId); + out = runSample("querywithstruct", databaseId); assertThat(out).startsWith("6\n"); - out = runSample("querywitharrayofstruct"); + out = runSample("querywitharrayofstruct", databaseId); assertThat(out).startsWith("8\n7\n6"); - out = runSample("querystructfield"); + out = runSample("querystructfield", databaseId); assertThat(out).startsWith("6\n"); - out = runSample("querynestedstructfield"); + out = runSample("querynestedstructfield", databaseId); assertThat(out).contains("6 Imagination\n"); assertThat(out).contains("9 Imagination\n"); - runSample("insertusingdml"); - out = runSample("querysingerstable"); + runSample("insertusingdml", databaseId); + out = runSample("querysingerstable", databaseId); assertThat(out).contains("Virginia Watson"); - runSample("updateusingdml"); - out = runSample("querymarketingbudget"); + runSample("updateusingdml", databaseId); + out = runSample("querymarketingbudget", databaseId); assertThat(out).contains("1 1 2000000"); - runSample("deleteusingdml"); - out = runSample("querysingerstable"); + runSample("deleteusingdml", databaseId); + out = runSample("querysingerstable", databaseId); assertThat(out).doesNotContain("Alice Trentor"); - out = runSample("updateusingdmlwithtimestamp"); + out = runSample("updateusingdmlwithtimestamp", databaseId); assertThat(out).contains("2 records updated"); - out = runSample("writeandreadusingdml"); + out = runSample("writeandreadusingdml", databaseId); assertThat(out).contains("Timothy Campbell"); - runSample("updateusingdmlwithstruct"); - out = runSample("querysingerstable"); + runSample("updateusingdmlwithstruct", databaseId); + out = runSample("querysingerstable", databaseId); assertThat(out).contains("Timothy Grant"); - runSample("writeusingdml"); - out = runSample("querysingerstable"); + 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"); + out = runSample("querywithparameter", databaseId); assertThat(out).contains("12 Melissa Garcia"); - runSample("writewithtransactionusingdml"); - out = runSample("querymarketingbudget"); + runSample("writewithtransactionusingdml", databaseId); + out = runSample("querymarketingbudget", databaseId); assertThat(out).contains("1 1 2200000"); assertThat(out).contains("2 2 550000"); - runSample("updateusingpartitioneddml"); - out = runSample("querymarketingbudget"); + runSample("updateusingpartitioneddml", databaseId); + out = runSample("querymarketingbudget", databaseId); assertThat(out).contains("1 1 2200000"); assertThat(out).contains("2 2 100000"); - runSample("deleteusingpartitioneddml"); - out = runSample("querysingerstable"); + 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"); + 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"); + out = runSample("createtablewithdatatypes", databaseId); assertThat(out).contains("Created Venues table in database"); - runSample("writedatatypesdata"); - out = runSample("querywitharray"); + 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"); + out = runSample("querywithbool", databaseId); assertThat(out).contains("19 Venue 19 true"); - out = runSample("querywithbytes"); + out = runSample("querywithbytes", databaseId); assertThat(out).contains("4 Venue 4"); - out = runSample("querywithdate"); + 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"); + out = runSample("querywithfloat", databaseId); assertThat(out).contains("4 Venue 4 0.8"); assertThat(out).contains("19 Venue 19 0.9"); - out = runSample("querywithint"); + out = runSample("querywithint", databaseId); assertThat(out).contains("19 Venue 19 6300"); assertThat(out).contains("42 Venue 42 3000"); - out = runSample("querywithstring"); + out = runSample("querywithstring", databaseId); assertThat(out).contains("42 Venue 42"); - out = runSample("querywithtimestampparameter"); + 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"); + out = runSample("querywithnumeric", databaseId); assertThat(out).contains("19 Venue 19 1200100"); assertThat(out).contains("42 Venue 42 390650.99"); - out = runSample("clientwithqueryoptions"); + out = runSample("clientwithqueryoptions", databaseId); assertThat(out).contains("1 1 Total Junk"); - out = runSample("querywithqueryoptions"); + out = runSample("querywithqueryoptions", databaseId); assertThat(out).contains("1 1 Total Junk"); + } - String backupName = - String.format( - "%s_%02d", - dbId.getDatabase(), LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR)); - BackupId backupId = BackupId.of(dbId.getInstanceId(), backupName); + @Test + public void testBackupSamples_withoutEncryption() { + String databaseId = idGenerator.generateDatabaseId(); + DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); + String restoreDatabaseId = idGenerator.generateDatabaseId(); + String backupId = idGenerator.generateBackupId(); - out = runSample("createbackup"); - assertThat(out).contains("Created backup [" + backupId + "]"); + try { + assertThat(instanceId).isNotNull(); + assertThat(databaseId).isNotNull(); - out = runSample("cancelcreatebackup"); - assertThat(out).contains( - "Backup operation for [" + backupId + "_cancel] successfully"); + System.out.println("Creating Database ..."); + String out = runSample("createdatabase", databaseId); + assertThat(out).contains("Created database"); + assertThat(out).contains(dbId.getName()); - // TODO: remove try-catch when filtering on metadata fields works. - try { - out = runSample("listbackupoperations"); - assertThat(out).contains( - String.format( - "Backup %s on database %s pending:", - backupId.getName(), - 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"); - } + BackupName backupName = BackupName.of(projectId, instanceId, backupId); - out = runSample("listbackups"); - assertThat(out).contains("All backups:"); - assertThat(out).contains( - String.format("All backups with backup name containing \"%s\":", backupId.getBackup())); - 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 7 times. - assertThat(countOccurrences(out, backupId.getName())).isEqualTo(7); - - // 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) { + 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 { - out = runSample("restorebackup"); + System.out.println("List Backup Operations ..."); + out = runSample("listbackupoperations", databaseId, backupId); assertThat(out).contains( - "Restored database [" - + dbId.getName() - + "] from [" - + backupId.getName() - + "]"); - restored = true; - break; + 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) { - 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; + 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; } - Uninterruptibles.sleepUninterruptibly(60L, TimeUnit.SECONDS); - } else { - throw e; } } - } - if (restored) { - out = runSample("listdatabaseoperations"); + 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( - "Database %s restored from backup", - DatabaseId.of( - dbId.getInstanceId(), - SpannerSample.createRestoredSampleDbId(dbId)) - .getName())); + 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(); - out = runSample("updatebackup"); - assertThat(out).contains( - String.format("Updated backup [" + backupId + "]")); + String out = runSample("createdatabase", databaseId); + assertThat(out).contains("Created database"); + assertThat(out).contains(dbId.getName()); - // 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. - dbClient.dropDatabase( - dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); + String backupId = idGenerator.generateBackupId(); - out = runSample("deletebackup"); - assertThat(out).contains("Deleted backup [" + backupId + "]"); + 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 = String.format("encrypted-test-%s", UUID.randomUUID()); + String instanceId = idGenerator.generateInstanceId(); InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient(); instanceAdminClient .createInstance(InstanceInfo.newBuilder(InstanceId.of(projectId, instanceId)) @@ -461,15 +526,15 @@ public void testEncryptedDatabaseAndBackupSamples() throws Exception { .get(); try { String out = SampleRunner - .runSample(() -> CreateDatabaseWithEncryptionKey.createDatabaseWithEncryptionKey(dbClient, - projectId, instanceId, encryptedDatabaseId, key)); + .runSample(() -> SpannerSample.createDatabase( + databaseAdminClient, InstanceName.of(projectId, instanceId), databaseId)); assertThat(out).contains(String.format( - "Database projects/%s/instances/%s/databases/%s created with encryption key %s", - projectId, instanceId, encryptedDatabaseId, key)); + "Created database [%s]", DatabaseName.of(projectId, instanceId, databaseId))); out = SampleRunner.runSampleWithRetry( - () -> CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(dbClient, projectId, - instanceId, encryptedDatabaseId, encryptedBackupId, key), + () -> 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 " @@ -477,14 +542,14 @@ public void testEncryptedDatabaseAndBackupSamples() throws Exception { projectId, instanceId, encryptedBackupId, key)); out = SampleRunner.runSampleWithRetry( - () -> RestoreBackupWithEncryptionKey.restoreBackupWithEncryptionKey(dbClient, projectId, - instanceId, encryptedBackupId, encryptedRestoreId, key), + () -> 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, encryptedDatabaseId, projectId, instanceId, encryptedRestoreId, + 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 @@ -494,13 +559,43 @@ public void testEncryptedDatabaseAndBackupSamples() throws Exception { } } + @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 { - for (Backup backup : dbClient.listBackups(instanceId).iterateAll()) { + InstanceName instanceName = InstanceName.of(projectId, instanceId); + for (Backup backup : databaseAdminClient.listBackups(instanceName.toString()).iterateAll()) { int attempts = 0; while (attempts < 30) { try { attempts++; - backup.delete(); + databaseAdminClient.deleteBackup(backup.getName()); break; } catch (SpannerException e) { if (e.getErrorCode() == ErrorCode.FAILED_PRECONDITION && e.getMessage() @@ -528,6 +623,9 @@ private String runSampleRunnable(Runnable sample) { @Test public void testCreateInstanceSample() { + String databaseId = idGenerator.generateDatabaseId(); + DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); + String instanceId = formatForTest("sample-inst"); String out = runSampleRunnable(() -> { @@ -567,6 +665,7 @@ static String formatForTest(String name) { } static class ShouldRetryBackupOperation implements Predicate { + private static final int MAX_ATTEMPTS = 20; private int attempts = 0; diff --git a/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java index 7c059bec1f..0acbf19893 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java @@ -16,11 +16,20 @@ package com.example.spanner; +import static com.example.spanner.SpannerSampleIT.formatForTest; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.example.spanner.CustomTimeoutAndRetrySettingsExample; +import com.example.spanner.QueryWithJsonParameterSample; +import com.example.spanner.QueryWithNumericParameterSample; +import com.example.spanner.StatementTimeoutExample; +import com.example.spanner.TransactionTimeoutExample; +import com.example.spanner.UpdateJsonDataSample; +import com.example.spanner.UpdateNumericDataSample; +import com.example.spanner.admin.archived.SpannerSampleIT; import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; @@ -56,7 +65,7 @@ public class SpannerStandaloneExamplesIT { // The instance needs to exist for tests to pass. private static String instanceId = System.getProperty("spanner.test.instance"); private static String baseDatabaseId = System.getProperty("spanner.sample.database", "mysample"); - private static String databaseId = SpannerSampleIT.formatForTest(baseDatabaseId); + private static String databaseId = formatForTest(baseDatabaseId); private static DatabaseId dbId; private static DatabaseAdminClient dbClient; private static Spanner spanner; @@ -183,7 +192,7 @@ public void addNumericColumn_shouldSuccessfullyAddColumn() () -> { try { AddNumericColumnSample.addNumericColumn( - spanner.getDatabaseAdminClient(), instanceId, databaseId); + spanner.getOptions().getProjectId(), instanceId, databaseId); } catch (ExecutionException e) { System.out.printf( "Adding column `Revenue` failed: %s%n", e.getCause().getMessage()); @@ -252,7 +261,7 @@ public void addJsonColumn_shouldSuccessfullyAddColumn() () -> { try { AddJsonColumnSample.addJsonColumn( - spanner.getDatabaseAdminClient(), instanceId, databaseId); + spanner.getOptions().getProjectId(), instanceId, databaseId); } catch (ExecutionException e) { System.out.printf( "Adding column `VenueDetails` failed: %s%n", e.getCause().getMessage()); diff --git a/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseSampleIT.java index 30ad8eec6a..b53414a2f5 100644 --- a/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseSampleIT.java @@ -20,40 +20,51 @@ import static org.junit.Assert.assertTrue; import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.DatabaseInfo.DatabaseField; +import com.google.common.collect.Lists; +import com.google.protobuf.FieldMask; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; -import java.util.Collections; +import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class UpdateDatabaseSampleIT extends SampleTestBase { +public class UpdateDatabaseSampleIT extends SampleTestBaseV2 { @Test public void testUpdateDatabase() throws Exception { // Create database final String databaseId = idGenerator.generateDatabaseId(); databaseAdminClient - .createDatabase(instanceId, databaseId, Collections.emptyList()) + .createDatabaseAsync(getInstanceName(projectId, instanceId), + "CREATE DATABASE `" + databaseId + "`") .get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> UpdateDatabaseSample.updateDatabase(projectId, instanceId, databaseId)); - - DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); assertTrue( "Expected that database would have been updated. Output received was " + out, - out.contains(String.format("Updated database %s", dbId))); + out.contains(String.format( + "Updated database %s", DatabaseName.of(projectId, instanceId, databaseId)))); // Cleanup - Database databaseToUpdate = - databaseAdminClient.newDatabaseBuilder(dbId).disableDropProtection().build(); + final com.google.spanner.admin.database.v1.Database database = + com.google.spanner.admin.database.v1.Database.newBuilder() + .setName(DatabaseName.of(projectId, instanceId, databaseId).toString()) + .setEnableDropProtection(false).build(); + final UpdateDatabaseRequest updateDatabaseRequest = + UpdateDatabaseRequest.newBuilder() + .setDatabase(database) + .setUpdateMask( + FieldMask.newBuilder().addAllPaths( + Lists.newArrayList("enable_drop_protection")).build()) + .build(); + OperationFuture operation = - databaseAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); + databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest); Database updatedDb = operation.get(5, TimeUnit.MINUTES); - assertFalse(updatedDb.isDropProtectionEnabled()); + assertFalse(updatedDb.getEnableDropProtection()); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSampleIT.java index ede90a5ec3..2953e02935 100644 --- a/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSampleIT.java @@ -18,38 +18,35 @@ import static org.junit.Assert.assertTrue; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.InstanceConfigId; -import java.util.Collections; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.instance.v1.InstanceConfig; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class UpdateDatabaseWithDefaultLeaderSampleIT extends SampleTestBase { +public class UpdateDatabaseWithDefaultLeaderSampleIT extends SampleTestBaseV2 { @Test public void testUpdateDatabaseWithDefaultLeader() throws Exception { // Create database final String databaseId = idGenerator.generateDatabaseId(); final Database createdDatabase = databaseAdminClient - .createDatabase(multiRegionalInstanceId, databaseId, Collections.emptyList()) + .createDatabaseAsync(getInstanceName(projectId, multiRegionalInstanceId), + "CREATE DATABASE `" + databaseId + "`") .get(5, TimeUnit.MINUTES); final String defaultLeader = createdDatabase.getDefaultLeader(); // Finds a possible new leader option - final InstanceConfigId instanceConfigId = instanceAdminClient - .getInstance(multiRegionalInstanceId) - .getInstanceConfigId(); - final InstanceConfig config = instanceAdminClient - .getInstanceConfig(instanceConfigId.getInstanceConfig()); - final String newLeader = config - .getLeaderOptions() - .stream() - .filter(leader -> !leader.equals(defaultLeader)) - .findFirst() - .orElseThrow(() -> - new RuntimeException("Expected to find a leader option different than " + defaultLeader) - ); + final String instanceConfigId = + instanceAdminClient.getInstance(getInstanceName(projectId, multiRegionalInstanceId)) + .getConfig(); + final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); + final String newLeader = + config.getLeaderOptionsList().stream() + .filter(leader -> !leader.equals(defaultLeader)) + .findFirst().orElseThrow(() -> + new RuntimeException("Expected to find a leader option different than " + + defaultLeader) + ); // Runs sample final String out = SampleRunner.runSample(() -> UpdateDatabaseWithDefaultLeaderSample diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSampleIT.java similarity index 77% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSampleIT.java index 2295119b69..89c10cc460 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/AlterTableWithForeignKeyDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/AlterTableWithForeignKeyDeleteCascadeSampleIT.java @@ -14,31 +14,28 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; import com.example.spanner.SampleTestBase; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.DatabaseDialect; -import com.google.spanner.admin.database.v1.InstanceName; import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class AlterTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBaseV2 { +public class AlterTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBase { @Test public void testAlterTableWithForeignKeyDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setCreateStatement("CREATE DATABASE `" + databaseId + "`") - .setParent(InstanceName.of(projectId, instanceId).toString()) - .addAllExtraStatements(Arrays.asList( + databaseAdminClient + .createDatabase( + instanceId, + databaseId, + Arrays.asList( "CREATE TABLE Customers (\n" + " CustomerId INT64 NOT NULL,\n" + " CustomerName STRING(62) NOT NULL,\n" @@ -50,15 +47,15 @@ public void testAlterTableWithForeignKeyDeleteCascade() throws Exception { + " CONSTRAINT FKShoppingCartsCustomerId" + " FOREIGN KEY (CustomerId)\n" + " REFERENCES Customers (CustomerId)\n" - + " ) PRIMARY KEY (CartId)\n")).build(); - databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); + + " ) PRIMARY KEY (CartId)\n")) + .get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> AlterTableWithForeignKeyDeleteCascadeSample.alterForeignKeyDeleteCascadeConstraint( - projectId, instanceId, databaseId)); + databaseAdminClient, instanceId, databaseId)); assertTrue( "Expected to have created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSampleIT.java new file mode 100644 index 0000000000..12b7c5044d --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithDefaultLeaderSampleIT.java @@ -0,0 +1,61 @@ +/* + * 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.archived; + +import static org.junit.Assert.assertTrue; + +import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.InstanceConfigId; +import org.junit.Test; + +public class CreateDatabaseWithDefaultLeaderSampleIT extends SampleTestBase { + + @Test + public void testCreateDatabaseWithDefaultLeader() throws Exception { + final String databaseId = idGenerator.generateDatabaseId(); + + // Finds possible default leader + final InstanceConfigId instanceConfigId = instanceAdminClient + .getInstance(multiRegionalInstanceId) + .getInstanceConfigId(); + final InstanceConfig config = instanceAdminClient + .getInstanceConfig(instanceConfigId.getInstanceConfig()); + assertTrue( + "Expected instance config " + instanceConfigId + " to have at least one leader option", + config.getLeaderOptions().size() > 0 + ); + final String defaultLeader = config.getLeaderOptions().get(0); + + // Runs sample + final String out = SampleRunner.runSample(() -> + CreateDatabaseWithDefaultLeaderSample.createDatabaseWithDefaultLeader( + projectId, + multiRegionalInstanceId, + databaseId, + defaultLeader + ) + ); + + assertTrue( + "Expected created database to have default leader " + defaultLeader + "." + + " Output received was " + out, + out.contains("Default leader: " + defaultLeader) + ); + } +} diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithVersionRetentionPeriodSampleIT.java similarity index 84% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithVersionRetentionPeriodSampleIT.java index 64400fbf21..448b1c10c4 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithVersionRetentionPeriodSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateDatabaseWithVersionRetentionPeriodSampleIT.java @@ -14,20 +14,21 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static com.google.common.truth.Truth.assertThat; import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** - * Integration tests for {@link com.example.spanner.CreateDatabaseWithVersionRetentionPeriodSample} + * Integration tests for {@link CreateDatabaseWithVersionRetentionPeriodSample} */ @RunWith(JUnit4.class) -public class CreateDatabaseWithVersionRetentionPeriodSampleIT extends SampleTestBaseV2 { +public class CreateDatabaseWithVersionRetentionPeriodSampleIT extends SampleTestBase { @Test public void createsDatabaseWithVersionRetentionPeriod() throws Exception { @@ -36,7 +37,7 @@ public void createsDatabaseWithVersionRetentionPeriod() throws Exception { final String out = SampleRunner.runSample(() -> CreateDatabaseWithVersionRetentionPeriodSample .createDatabaseWithVersionRetentionPeriod( - projectId, instanceId, databaseId, versionRetentionPeriod + databaseAdminClient, instanceId, databaseId, versionRetentionPeriod )); assertThat(out).contains( diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigSampleIT.java similarity index 58% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigSampleIT.java index 86224cfd30..041d0c5201 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceWithAutoscalingConfigSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateInstanceWithAutoscalingConfigSampleIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,31 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static com.google.common.truth.Truth.assertThat; import com.example.spanner.SampleRunner; -import com.google.spanner.admin.database.v1.InstanceName; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.InstanceId; +import java.util.UUID; import org.junit.Test; -public class CreateInstanceWithAutoscalingConfigSampleIT extends SampleTestBaseV2 { +public class CreateInstanceWithAutoscalingConfigSampleIT extends SampleTestBase { @Test public void testCreateInstanceWithAutoscalingConfig() throws Exception { - String instanceId = idGenerator.generateInstanceId(); + String instanceId = String.format("autoscaler-%s", UUID.randomUUID()); String out = SampleRunner.runSample( - () -> CreateInstanceWithAutoscalingConfigExample.createInstance(projectId, instanceId)); + () -> { + try { + CreateInstanceWithAutoscalingConfigExample.createInstance(projectId, instanceId); + } finally { + spanner.getInstanceAdminClient().deleteInstance(instanceId); + } + }); assertThat(out) - .contains(String.format("Autoscaler instance %s", - InstanceName.of(projectId, instanceId).toString())); + .contains(String.format("Autoscaler instance %s", InstanceId.of(projectId, instanceId))); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSampleIT.java similarity index 71% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSampleIT.java index 6fd9aeddb1..a6cdee1947 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateTableWithForeignKeyDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CreateTableWithForeignKeyDeleteCascadeSampleIT.java @@ -14,37 +14,34 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; import com.example.spanner.SampleTestBase; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.InstanceName; import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class CreateTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBaseV2 { +public class CreateTableWithForeignKeyDeleteCascadeSampleIT extends SampleTestBase { @Test public void testCreateTableWithForeignKeyDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setCreateStatement("CREATE DATABASE `" + databaseId + "`") - .setParent(InstanceName.of(projectId, instanceId).toString()).build(); - databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); + databaseAdminClient + .createDatabase(instanceId, databaseId, Arrays.asList()) + .get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> CreateTableWithForeignKeyDeleteCascadeSample - .createForeignKeyDeleteCascadeConstraint(projectId, instanceId, databaseId)); + .createForeignKeyDeleteCascadeConstraint( + databaseAdminClient, instanceId, databaseId)); assertTrue( "Expected to have created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CustomInstanceConfigSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CustomInstanceConfigSampleIT.java similarity index 64% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/CustomInstanceConfigSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/CustomInstanceConfigSampleIT.java index 8a01d2a7a6..a56b72b75f 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CustomInstanceConfigSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/CustomInstanceConfigSampleIT.java @@ -1,11 +1,11 @@ /* - * Copyright 2024 Google LLC + * Copyright 2022 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 + * 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, @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -@RunWith(JUnit4.class) -public class CustomInstanceConfigSampleIT extends SampleTestBaseV2 { +public class CustomInstanceConfigSampleIT extends SampleTestBase { @Test public void testCustomInstanceConfigOperations() throws Exception { @@ -38,37 +36,26 @@ public void testCustomInstanceConfigOperations() throws Exception { projectId, instanceConfigName, customInstanceConfigId)); assertTrue(out1.contains("Created instance configuration")); - // Fetch the instance config that was created above. - final String out2 = - SampleRunner.runSample( - () -> GetInstanceConfigSample.getInstanceConfig(projectId, instanceConfigName)); - assertTrue(out2.contains("Available leader options for instance config")); - - // Fetch the instance config that was created above. - final String out3 = - SampleRunner.runSample( - () -> ListInstanceConfigsSample.listInstanceConfigs(projectId)); - assertTrue(out3.contains("Available leader options for instance config")); - // List the instance config operations. - final String out4 = + final String out2 = SampleRunner.runSample( () -> ListInstanceConfigOperationsSample.listInstanceConfigOperations(projectId)); - assertTrue(out4.contains("Obtained list of instance config operations")); + assertTrue(out2.contains("List instance config operation")); // Update display name to a randomly generated instance config id. - final String out5 = + final String out3 = SampleRunner.runSample( () -> - UpdateInstanceConfigSample.updateInstanceConfig(projectId, customInstanceConfigId)); - assertTrue(out5.contains("Updated instance configuration")); + UpdateInstanceConfigSample.updateInstanceConfig( + projectId, customInstanceConfigId)); + assertTrue(out3.contains("Updated instance configuration")); // Delete the created instance config. - final String out6 = + final String out4 = SampleRunner.runSample( () -> DeleteInstanceConfigSample.deleteInstanceConfig(projectId, customInstanceConfigId)); - assertTrue(out6.contains("Deleted instance configuration")); + assertTrue(out4.contains("Deleted instance configuration")); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/DatabaseRolesIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/DatabaseRolesIT.java similarity index 82% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/DatabaseRolesIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/DatabaseRolesIT.java index 93b19a2b33..3e40189d94 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/DatabaseRolesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/DatabaseRolesIT.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; -import com.google.common.collect.Lists; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; @@ -35,24 +35,20 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Integration tests for FGAC samples for GoogleStandardSql dialect. - */ +/** Integration tests for FGAC samples for GoogleStandardSql dialect. */ @RunWith(JUnit4.class) -public class DatabaseRolesIT extends SampleTestBaseV2 { +public class DatabaseRolesIT extends SampleTestBase { private static DatabaseId databaseId; @BeforeClass public static void createTestDatabase() throws Exception { final String database = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setParent( - com.google.spanner.admin.database.v1.InstanceName.of(projectId, instanceId) - .toString()) - .setCreateStatement("CREATE DATABASE `" + database + "`") - .addAllExtraStatements(Lists.newArrayList( + databaseAdminClient + .createDatabase( + instanceId, + database, + ImmutableList.of( "CREATE TABLE Singers (" + " SingerId INT64 NOT NULL," + " FirstName STRING(1024)," @@ -67,8 +63,8 @@ public static void createTestDatabase() throws Exception { + " AlbumTitle STRING(MAX)," + " MarketingBudget INT64" + ") PRIMARY KEY (SingerId, AlbumId)," - + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build(); - databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")) + .get(10, TimeUnit.MINUTES); databaseId = DatabaseId.of(projectId, instanceId, database); } @@ -109,7 +105,7 @@ public void testAddAndDropDatabaseRole() throws Exception { SampleRunner.runSample( () -> AddAndDropDatabaseRole.addAndDropDatabaseRole( - projectId, instanceId, databaseId.getDatabase(), "new_parent", "new_child")); + projectId, instanceId, databaseId.getDatabase(), "new-parent", "new-child")); assertTrue(out.contains("Created roles new_parent and new_child and granted privileges")); assertTrue(out.contains("Revoked privileges and dropped role new_child")); } @@ -121,6 +117,6 @@ public void testListDatabaseRoles() throws Exception { () -> ListDatabaseRoles.listDatabaseRoles( projectId, instanceId, databaseId.getDatabase())); - assertTrue(out.contains("Obtained role ")); + assertTrue(out.contains("new_parent")); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSampleIT.java similarity index 77% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSampleIT.java index f2dae05237..a4ce31c9d8 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/DropForeignKeyConstraintDeleteCascadeSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/DropForeignKeyConstraintDeleteCascadeSampleIT.java @@ -14,31 +14,28 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; import com.example.spanner.SampleTestBase; -import com.google.common.collect.Lists; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.InstanceName; import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class DropForeignKeyConstraintDeleteCascadeSampleIT extends SampleTestBaseV2 { +public class DropForeignKeyConstraintDeleteCascadeSampleIT extends SampleTestBase { @Test public void testDropForeignKeyConstraintDeleteCascade() throws Exception { // Creates database final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setCreateStatement("CREATE DATABASE `" + databaseId + "`") - .setParent(InstanceName.of(projectId, instanceId).toString()) - .addAllExtraStatements(Lists.newArrayList( + databaseAdminClient + .createDatabase( + instanceId, + databaseId, + Arrays.asList( "CREATE TABLE Customers (\n" + " CustomerId INT64 NOT NULL,\n" + " CustomerName STRING(62) NOT NULL,\n" @@ -50,15 +47,15 @@ public void testDropForeignKeyConstraintDeleteCascade() throws Exception { + " CONSTRAINT FKShoppingCartsCustomerName" + " FOREIGN KEY (CustomerName)\n" + " REFERENCES Customers (CustomerName) ON DELETE CASCADE\n" - + " ) PRIMARY KEY (CartId)\n")).build(); - databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); + + " ) PRIMARY KEY (CartId)\n")) + .get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> DropForeignKeyConstraintDeleteCascadeSample.deleteForeignKeyDeleteCascadeConstraint( - projectId, instanceId, databaseId)); + databaseAdminClient, instanceId, databaseId)); assertTrue( "Expected to have dropped foreign-key constraints from tables in created database " diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/archived/EncryptionKeyIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/EncryptionKeyIT.java new file mode 100644 index 0000000000..ffd0771fa0 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/EncryptionKeyIT.java @@ -0,0 +1,127 @@ +/* + * 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.archived; + +import static com.google.common.truth.Truth.assertThat; + +import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; +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 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 CreateDatabaseWithEncryptionKey}, {@link + * CreateBackupWithEncryptionKey} and {@link RestoreBackupWithEncryptionKey} + */ +@RunWith(JUnit4.class) +@Ignore +public class EncryptionKeyIT extends SampleTestBase { + + 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(() -> + CreateDatabaseWithEncryptionKey.createDatabaseWithEncryptionKey( + databaseAdminClient, + projectId, + instanceId, + databaseId, + key + )); + assertThat(out).contains( + "Database projects/" + projectId + "/instances/" + instanceId + "/databases/" + databaseId + + " created with encryption key " + key); + + 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/GetDatabaseDdlSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/GetDatabaseDdlSampleIT.java similarity index 56% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/GetDatabaseDdlSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/GetDatabaseDdlSampleIT.java index 8769a12598..404aceca72 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/GetDatabaseDdlSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/GetDatabaseDdlSampleIT.java @@ -14,49 +14,48 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; -import com.google.common.collect.Lists; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.instance.v1.InstanceConfig; -import com.google.spanner.admin.instance.v1.InstanceName; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.InstanceConfigId; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class GetDatabaseDdlSampleIT extends SampleTestBaseV2 { +public class GetDatabaseDdlSampleIT extends SampleTestBase { @Test public void testGetDatabaseDdl() throws Exception { // Finds a possible new leader option - final String instanceConfigId = instanceAdminClient.getInstance( - InstanceName.of(projectId, multiRegionalInstanceId)).getConfig(); - final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); + final InstanceConfigId instanceConfigId = instanceAdminClient + .getInstance(multiRegionalInstanceId) + .getInstanceConfigId(); + final InstanceConfig config = instanceAdminClient + .getInstanceConfig(instanceConfigId.getInstanceConfig()); assertTrue( "Expected instance config " + instanceConfigId + " to have at least one leader option", - config.getLeaderOptionsList().size() > 0 + config.getLeaderOptions().size() > 0 ); - final String defaultLeader = config.getLeaderOptions(0); + final String defaultLeader = config.getLeaderOptions().get(0); // Creates database final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setParent( - com.google.spanner.admin.database.v1.InstanceName.of(projectId, - multiRegionalInstanceId).toString()) - .setCreateStatement("CREATE DATABASE `" + databaseId + "`") - .addAllExtraStatements(Lists.newArrayList( - "CREATE TABLE Singers (Id INT64 NOT NULL) PRIMARY KEY (Id)", - "ALTER DATABASE `" - + databaseId - + "` SET OPTIONS ( default_leader = '" - + defaultLeader - + "')" - )).build(); - databaseAdminClient.createDatabaseAsync(request).get(5, TimeUnit.MINUTES); + databaseAdminClient.createDatabase( + multiRegionalInstanceId, + databaseId, + Arrays.asList( + "CREATE TABLE Singers (Id INT64 NOT NULL) PRIMARY KEY (Id)", + "ALTER DATABASE `" + + databaseId + + "` SET OPTIONS ( default_leader = '" + + defaultLeader + + "')" + ) + ).get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample(() -> GetDatabaseDdlSample diff --git a/samples/snippets/src/test/java/com/example/spanner/GetInstanceConfigSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/GetInstanceConfigSampleIT.java similarity index 90% rename from samples/snippets/src/test/java/com/example/spanner/GetInstanceConfigSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/GetInstanceConfigSampleIT.java index 2690093de8..ac3598f549 100644 --- a/samples/snippets/src/test/java/com/example/spanner/GetInstanceConfigSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/GetInstanceConfigSampleIT.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.example.spanner; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; +import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; import org.junit.Test; public class GetInstanceConfigSampleIT extends SampleTestBase { diff --git a/samples/snippets/src/test/java/com/example/spanner/ListInstanceConfigsSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/ListInstanceConfigsSampleIT.java similarity index 89% rename from samples/snippets/src/test/java/com/example/spanner/ListInstanceConfigsSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/ListInstanceConfigsSampleIT.java index ef2b730cd3..8dcee20313 100644 --- a/samples/snippets/src/test/java/com/example/spanner/ListInstanceConfigsSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/ListInstanceConfigsSampleIT.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.example.spanner; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; +import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; import org.junit.Test; public class ListInstanceConfigsSampleIT extends SampleTestBase { diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgCaseSensitivitySampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgCaseSensitivitySampleIT.java similarity index 62% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/PgCaseSensitivitySampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/PgCaseSensitivitySampleIT.java index 88337ba8c6..100014643d 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgCaseSensitivitySampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgCaseSensitivitySampleIT.java @@ -14,27 +14,30 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.DatabaseDialect; -import com.google.spanner.admin.database.v1.InstanceName; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; +import java.util.Collections; import org.junit.Test; -public class PgCaseSensitivitySampleIT extends SampleTestBaseV2 { +public class PgCaseSensitivitySampleIT extends SampleTestBase { @Test public void testPgCaseSensitivitySample() throws Exception { final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setCreateStatement(getCreateDatabaseStatement(databaseId, DatabaseDialect.POSTGRESQL)) - .setParent(InstanceName.of(projectId, instanceId).toString()) - .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build(); - databaseAdminClient.createDatabaseAsync(request).get(); + databaseAdminClient + .createDatabase( + databaseAdminClient + .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) + .setDialect(Dialect.POSTGRESQL) + .build(), + Collections.emptyList()) + .get(); final String out = SampleRunner.runSample( diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgInterleavedTableSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgInterleavedTableSampleIT.java similarity index 61% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/PgInterleavedTableSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/PgInterleavedTableSampleIT.java index 59a0f4a524..b7d229a1b8 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/PgInterleavedTableSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgInterleavedTableSampleIT.java @@ -14,27 +14,30 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.DatabaseDialect; -import com.google.spanner.admin.database.v1.InstanceName; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; +import java.util.Collections; import org.junit.Test; -public class PgInterleavedTableSampleIT extends SampleTestBaseV2 { +public class PgInterleavedTableSampleIT extends SampleTestBase { @Test public void testPgInterleavedTableSample() throws Exception { final String databaseId = idGenerator.generateDatabaseId(); - final CreateDatabaseRequest request = - CreateDatabaseRequest.newBuilder() - .setCreateStatement(getCreateDatabaseStatement(databaseId, DatabaseDialect.POSTGRESQL)) - .setParent(InstanceName.of(projectId, instanceId).toString()) - .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build(); - databaseAdminClient.createDatabaseAsync(request).get(); + databaseAdminClient + .createDatabase( + databaseAdminClient + .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId)) + .setDialect(Dialect.POSTGRESQL) + .build(), + Collections.emptyList()) + .get(); final String out = SampleRunner.runSample( diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgSpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgSpannerSampleIT.java new file mode 100644 index 0000000000..72900b5e41 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/PgSpannerSampleIT.java @@ -0,0 +1,304 @@ +/* + * 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.archived; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import org.junit.AfterClass; +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 { + 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 databaseId = formatForTest(baseDbId); + private static final String encryptedDatabaseId = formatForTest(baseDbId); + private static final String encryptedBackupId = formatForTest(baseDbId); + private static final String encryptedRestoreId = formatForTest(baseDbId); + static Spanner spanner; + static DatabaseId dbId; + static DatabaseAdminClient dbClient; + + @BeforeClass + public static void setUp() { + SpannerOptions options = + SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build(); + spanner = options.getService(); + dbClient = spanner.getDatabaseAdminClient(); + dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId); + // 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(PgSpannerSampleIT.instanceId).iterateAll()) { + if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(), + TimeUnit.SECONDS) > 24) { + if (db.getId().getDatabase().length() >= DBID_LENGTH) { + if (samplePattern.matcher(toComparableId(PgSpannerSampleIT.baseDbId, + db.getId().getDatabase())).matches()) { + db.drop(); + } + if (restoredPattern.matcher(toComparableId("restored", db.getId().getDatabase())) + .matches()) { + db.drop(); + } + } + } + } + } + + @AfterClass + public static void tearDown() { + dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); + dbClient.dropDatabase( + dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); + dbClient.dropDatabase(instanceId, encryptedDatabaseId); + dbClient.dropDatabase(instanceId, encryptedRestoreId); + dbClient.deleteBackup(instanceId, encryptedBackupId); + spanner.close(); + } + + 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); + } + + private String runSample(String command) { + final PrintStream stdOut = System.out; + final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + final PrintStream out = new PrintStream(bout); + System.setOut(out); + System.out.println(instanceId + ":" + databaseId); + PgSpannerSample.main(new String[]{command, instanceId, databaseId}); + System.setOut(stdOut); + return bout.toString(); + } + + @Test + public void testSample() throws Exception { + assertThat(instanceId).isNotNull(); + assertThat(databaseId).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/SequenceSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/SequenceSampleIT.java similarity index 69% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/SequenceSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/SequenceSampleIT.java index b3f4df004d..e1e527042a 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/SequenceSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/SequenceSampleIT.java @@ -14,15 +14,17 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static com.example.spanner.SampleRunner.runSample; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import com.example.spanner.SampleTestBase; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Dialect; import com.google.common.collect.ImmutableList; -import com.google.spanner.admin.database.v1.CreateDatabaseRequest; -import com.google.spanner.admin.database.v1.DatabaseDialect; +import java.util.Collections; import java.util.HashSet; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -37,23 +39,23 @@ * dialects. */ @RunWith(Parameterized.class) -public class SequenceSampleIT extends SampleTestBaseV2 { +public class SequenceSampleIT extends SampleTestBase { - private static String databaseId; + private static DatabaseId databaseId; /** * Set of dialects for which database has already been created in this test suite. This helps in * limiting the number of databases created per dialect to one. */ - private static final HashSet dbInitializedDialects = new HashSet<>(); + private static final HashSet dbInitializedDialects = new HashSet<>(); @Parameters(name = "dialect = {0}") - public static Iterable data() { - return ImmutableList.of(DatabaseDialect.GOOGLE_STANDARD_SQL, DatabaseDialect.POSTGRESQL); + public static Iterable data() { + return ImmutableList.of(Dialect.GOOGLE_STANDARD_SQL, Dialect.POSTGRESQL); } @Parameter(0) - public static DatabaseDialect dialect; + public static Dialect dialect; @Before public void createTestDatabase() throws Exception { @@ -62,36 +64,37 @@ public void createTestDatabase() throws Exception { return; } dbInitializedDialects.add(dialect); - databaseId = idGenerator.generateDatabaseId(); - CreateDatabaseRequest createDatabaseRequest = - CreateDatabaseRequest.newBuilder() - .setParent(getInstanceName(projectId, instanceId)) - .setCreateStatement(getCreateDatabaseStatement(databaseId, dialect)) - .setDatabaseDialect(dialect).build(); + final String database = idGenerator.generateDatabaseId(); databaseAdminClient - .createDatabaseAsync(createDatabaseRequest) + .createDatabase( + databaseAdminClient + .newDatabaseBuilder(DatabaseId.of(projectId, instanceId, database)) + .setDialect(dialect) + .build(), + Collections.emptyList()) .get(10, TimeUnit.MINUTES); + databaseId = DatabaseId.of(projectId, instanceId, database); } @Test public void createSequence() throws Exception { String out; - if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> CreateSequenceSample.createSequence( - projectId, instanceId, databaseId)); + projectId, instanceId, databaseId.getDatabase())); } else { out = runSample( () -> PgCreateSequenceSample.pgCreateSequence( - projectId, instanceId, databaseId)); + projectId, instanceId, databaseId.getDatabase())); } assertTrue( out.contains( - "Created Seq sequence and Customers table, where the key column " + "Created Seq sequence and Customers table, where its key column " + "CustomerId uses the sequence as a default value")); assertEquals(out.split("Inserted customer record with CustomerId", -1).length - 1, 3); assertTrue(out.contains("Number of customer records inserted is: 3")); @@ -100,18 +103,18 @@ public void createSequence() throws Exception { @Test public void alterSequence() throws Exception { String out; - if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> AlterSequenceSample.alterSequence( - projectId, instanceId, databaseId)); + projectId, instanceId, databaseId.getDatabase())); } else { out = runSample( () -> PgAlterSequenceSample.pgAlterSequence( - projectId, instanceId, databaseId)); + projectId, instanceId, databaseId.getDatabase())); } assertTrue( out.contains("Altered Seq sequence to skip an inclusive range between 1000 and 5000000")); @@ -122,17 +125,17 @@ public void alterSequence() throws Exception { @Test public void dropSequence() throws Exception { String out; - if (dialect == DatabaseDialect.GOOGLE_STANDARD_SQL) { + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { out = runSample( () -> - DropSequenceSample.dropSequence(projectId, instanceId, databaseId)); + DropSequenceSample.dropSequence(projectId, instanceId, databaseId.getDatabase())); } else { out = runSample( () -> PgDropSequenceSample.pgDropSequence( - projectId, instanceId, databaseId)); + projectId, instanceId, databaseId.getDatabase())); } assertTrue( out.contains( diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/archived/SpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/SpannerSampleIT.java new file mode 100644 index 0000000000..66410cce4d --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/SpannerSampleIT.java @@ -0,0 +1,593 @@ +/* + * 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.archived; + +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.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +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.common.base.Preconditions; +import com.google.common.util.concurrent.Uninterruptibles; +import java.io.ByteArrayOutputStream; +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.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.LocalDate; +import org.threeten.bp.temporal.ChronoField; + +/** Unit tests for {@code SpannerSample} */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SpannerSampleIT { + 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 databaseId = formatForTest(baseDbId); + private static final String encryptedDatabaseId = formatForTest(baseDbId); + private static final String encryptedBackupId = formatForTest(baseDbId); + private static final String encryptedRestoreId = formatForTest(baseDbId); + private static final long STALE_INSTANCE_THRESHOLD_SECS = + TimeUnit.SECONDS.convert(24L, TimeUnit.HOURS); + static Spanner spanner; + static DatabaseId dbId; + static DatabaseAdminClient dbClient; + private static String key; + private long lastUpdateDataTimeInMillis; + + private String runSample(String command) 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}); + System.setOut(stdOut); + return bout.toString(); + } + + @BeforeClass + public static void setUp() throws Exception { + SpannerOptions options = + SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build(); + spanner = options.getService(); + dbClient = spanner.getDatabaseAdminClient(); + dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId); + // Delete stale test databases that have been created earlier by this test, but not deleted. + deleteStaleTestDatabases(instanceId, baseDbId); + 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(String instanceId, String baseDbId) { + Timestamp now = Timestamp.now(); + Pattern samplePattern = getTestDbIdPattern(baseDbId); + Pattern restoredPattern = getTestDbIdPattern("restored"); + for (Database db : dbClient.listDatabases(instanceId).iterateAll()) { + if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(), + TimeUnit.SECONDS) > 24) { + if (db.getId().getDatabase().length() >= DBID_LENGTH) { + if (samplePattern.matcher(toComparableId(baseDbId, db.getId().getDatabase())).matches()) { + db.drop(); + } + if (restoredPattern.matcher(toComparableId("restored", db.getId().getDatabase())) + .matches()) { + db.drop(); + } + } + } + } + } + + @AfterClass + public static void tearDown() throws Exception { + dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); + dbClient.dropDatabase( + dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); + dbClient.dropDatabase(instanceId, encryptedDatabaseId); + dbClient.dropDatabase(instanceId, encryptedRestoreId); + dbClient.deleteBackup(instanceId, encryptedBackupId); + spanner.close(); + } + + @Test + public void testSample() throws Exception { + assertThat(instanceId).isNotNull(); + assertThat(databaseId).isNotNull(); + String out = runSample("createdatabase"); + assertThat(out).contains("Created database"); + assertThat(out).contains(dbId.getName()); + + runSample("write"); + + out = runSample("delete"); + assertThat(out).contains("Records deleted."); + + runSample("write"); + + out = runSample("read"); + assertThat(out).contains("1 1 Total Junk"); + + out = runSample("query"); + assertThat(out).contains("1 1 Total Junk"); + runSample("addmarketingbudget"); + + // 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"); + out = runSample("readstaledata"); + assertThat(out).contains("1 1 NULL"); + runSample("writetransaction"); + out = runSample("querymarketingbudget"); + assertThat(out).contains("1 1 300000"); + assertThat(out).contains("2 2 300000"); + + runSample("addindex"); + out = runSample("queryindex"); + assertThat(out).contains("Go, Go, Go"); + assertThat(out).contains("Forever Hold Your Peace"); + assertThat(out).doesNotContain("Green"); + + out = runSample("readindex"); + assertThat(out).contains("Go, Go, Go"); + assertThat(out).contains("Forever Hold Your Peace"); + assertThat(out).contains("Green"); + + runSample("addstoringindex"); + out = runSample("readstoringindex"); + assertThat(out).contains("300000"); + + out = runSample("readonlytransaction"); + assertThat(out.replaceAll("[\r\n]+", " ")).containsMatch("(Total Junk.*){2}"); + + out = runSample("addcommittimestamp"); + assertThat(out).contains("Added LastUpdateTime as a commit timestamp column"); + + runSample("updatewithtimestamp"); + out = runSample("querywithtimestamp"); + assertThat(out).contains("1 1 1000000"); + assertThat(out).contains("2 2 750000"); + + out = runSample("createtablewithtimestamp"); + assertThat(out).contains("Created Performances table in database"); + + runSample("writewithtimestamp"); + out = runSample("queryperformancestable"); + 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"); + out = runSample("querywithstruct"); + assertThat(out).startsWith("6\n"); + + out = runSample("querywitharrayofstruct"); + assertThat(out).startsWith("8\n7\n6"); + + out = runSample("querystructfield"); + assertThat(out).startsWith("6\n"); + + out = runSample("querynestedstructfield"); + assertThat(out).contains("6 Imagination\n"); + assertThat(out).contains("9 Imagination\n"); + + runSample("insertusingdml"); + out = runSample("querysingerstable"); + assertThat(out).contains("Virginia Watson"); + + runSample("updateusingdml"); + out = runSample("querymarketingbudget"); + assertThat(out).contains("1 1 2000000"); + + runSample("deleteusingdml"); + out = runSample("querysingerstable"); + assertThat(out).doesNotContain("Alice Trentor"); + + out = runSample("updateusingdmlwithtimestamp"); + assertThat(out).contains("2 records updated"); + + out = runSample("writeandreadusingdml"); + assertThat(out).contains("Timothy Campbell"); + + runSample("updateusingdmlwithstruct"); + out = runSample("querysingerstable"); + assertThat(out).contains("Timothy Grant"); + + runSample("writeusingdml"); + out = runSample("querysingerstable"); + assertThat(out).contains("Melissa Garcia"); + assertThat(out).contains("Russell Morales"); + assertThat(out).contains("Jacqueline Long"); + assertThat(out).contains("Dylan Shaw"); + out = runSample("querywithparameter"); + assertThat(out).contains("12 Melissa Garcia"); + + runSample("writewithtransactionusingdml"); + out = runSample("querymarketingbudget"); + assertThat(out).contains("1 1 2200000"); + assertThat(out).contains("2 2 550000"); + + runSample("updateusingpartitioneddml"); + out = runSample("querymarketingbudget"); + assertThat(out).contains("1 1 2200000"); + assertThat(out).contains("2 2 100000"); + + 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"); + + out = runSample("updateusingbatchdml"); + assertThat(out).contains("1 record updated by stmt 0"); + assertThat(out).contains("1 record updated by stmt 1"); + + out = runSample("createtablewithdatatypes"); + assertThat(out).contains("Created Venues table in database"); + + runSample("writedatatypesdata"); + out = runSample("querywitharray"); + assertThat(out).contains("19 Venue 19 2020-11-01"); + assertThat(out).contains("42 Venue 42 2020-10-01"); + + out = runSample("querywithbool"); + assertThat(out).contains("19 Venue 19 true"); + + out = runSample("querywithbytes"); + assertThat(out).contains("4 Venue 4"); + + out = runSample("querywithdate"); + assertThat(out).contains("4 Venue 4 2018-09-02"); + assertThat(out).contains("42 Venue 42 2018-10-01"); + + out = runSample("querywithfloat"); + assertThat(out).contains("4 Venue 4 0.8"); + assertThat(out).contains("19 Venue 19 0.9"); + + out = runSample("querywithint"); + assertThat(out).contains("19 Venue 19 6300"); + assertThat(out).contains("42 Venue 42 3000"); + + out = runSample("querywithstring"); + assertThat(out).contains("42 Venue 42"); + + out = runSample("querywithtimestampparameter"); + assertThat(out).contains("4 Venue 4"); + assertThat(out).contains("19 Venue 19"); + assertThat(out).contains("42 Venue 42"); + + out = runSample("querywithnumeric"); + assertThat(out).contains("19 Venue 19 1200100"); + assertThat(out).contains("42 Venue 42 390650.99"); + + out = runSample("clientwithqueryoptions"); + assertThat(out).contains("1 1 Total Junk"); + out = runSample("querywithqueryoptions"); + assertThat(out).contains("1 1 Total Junk"); + + String backupName = + String.format( + "%s_%02d", + dbId.getDatabase(), LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR)); + BackupId backupId = BackupId.of(dbId.getInstanceId(), backupName); + + out = runSample("createbackup"); + assertThat(out).contains("Created backup [" + backupId + "]"); + + out = runSample("cancelcreatebackup"); + assertThat(out).contains( + "Backup operation for [" + backupId + "_cancel] successfully"); + + // TODO: remove try-catch when filtering on metadata fields works. + try { + out = runSample("listbackupoperations"); + assertThat(out).contains( + String.format( + "Backup %s on database %s pending:", + backupId.getName(), + 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"); + } + + out = runSample("listbackups"); + assertThat(out).contains("All backups:"); + assertThat(out).contains( + String.format("All backups with backup name containing \"%s\":", backupId.getBackup())); + 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 7 times. + assertThat(countOccurrences(out, backupId.getName())).isEqualTo(7); + + // 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 { + out = runSample("restorebackup"); + assertThat(out).contains( + "Restored database [" + + dbId.getName() + + "] from [" + + backupId.getName() + + "]"); + 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) { + out = runSample("listdatabaseoperations"); + assertThat(out).contains( + String.format( + "Database %s restored from backup", + DatabaseId.of( + dbId.getInstanceId(), + SpannerSample.createRestoredSampleDbId(dbId)) + .getName())); + } + + out = runSample("updatebackup"); + 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. + dbClient.dropDatabase( + dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId)); + + out = runSample("deletebackup"); + assertThat(out).contains("Deleted backup [" + backupId + "]"); + } + + @Test + public void testEncryptedDatabaseAndBackupSamples() throws Exception { + String projectId = spanner.getOptions().getProjectId(); + // 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 = String.format("encrypted-test-%s", UUID.randomUUID()); + 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(() -> CreateDatabaseWithEncryptionKey.createDatabaseWithEncryptionKey(dbClient, + projectId, instanceId, encryptedDatabaseId, key)); + assertThat(out).contains(String.format( + "Database projects/%s/instances/%s/databases/%s created with encryption key %s", + projectId, instanceId, encryptedDatabaseId, key)); + + out = SampleRunner.runSampleWithRetry( + () -> CreateBackupWithEncryptionKey.createBackupWithEncryptionKey(dbClient, projectId, + instanceId, encryptedDatabaseId, 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(dbClient, projectId, + instanceId, encryptedBackupId, encryptedRestoreId, 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, encryptedDatabaseId, projectId, instanceId, encryptedRestoreId, + 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); + } + } + + private static void deleteAllBackups(String instanceId) throws InterruptedException { + for (Backup backup : dbClient.listBackups(instanceId).iterateAll()) { + int attempts = 0; + while (attempts < 30) { + try { + attempts++; + backup.delete(); + 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 instanceId = formatForTest("sample-inst"); + String out = + runSampleRunnable(() -> { + try { + CreateInstanceExample.createInstance( + dbId.getInstanceId().getProject(), instanceId); + } 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; + } + } +} diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseSampleIT.java similarity index 53% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseSampleIT.java index acac98cde4..29a99ed3b8 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseSampleIT.java @@ -14,58 +14,49 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; +import com.example.spanner.SampleTestBase; +import com.example.spanner.admin.archived.UpdateDatabaseSample; import com.google.api.gax.longrunning.OperationFuture; -import com.google.common.collect.Lists; -import com.google.protobuf.FieldMask; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; -import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; +import java.util.Collections; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class UpdateDatabaseSampleIT extends SampleTestBaseV2 { +public class UpdateDatabaseSampleIT extends SampleTestBase { @Test public void testUpdateDatabase() throws Exception { // Create database final String databaseId = idGenerator.generateDatabaseId(); databaseAdminClient - .createDatabaseAsync(getInstanceName(projectId, instanceId), - "CREATE DATABASE `" + databaseId + "`") + .createDatabase(instanceId, databaseId, Collections.emptyList()) .get(5, TimeUnit.MINUTES); // Runs sample final String out = SampleRunner.runSample( () -> UpdateDatabaseSample.updateDatabase(projectId, instanceId, databaseId)); + + DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId); assertTrue( "Expected that database would have been updated. Output received was " + out, - out.contains(String.format( - "Updated database %s", DatabaseName.of(projectId, instanceId, databaseId)))); + out.contains(String.format("Updated database %s", dbId))); // Cleanup - final com.google.spanner.admin.database.v1.Database database = - com.google.spanner.admin.database.v1.Database.newBuilder() - .setName(DatabaseName.of(projectId, instanceId, databaseId).toString()) - .setEnableDropProtection(false).build(); - final UpdateDatabaseRequest updateDatabaseRequest = - UpdateDatabaseRequest.newBuilder() - .setDatabase(database) - .setUpdateMask( - FieldMask.newBuilder().addAllPaths( - Lists.newArrayList("enable_drop_protection")).build()) - .build(); - + Database databaseToUpdate = + databaseAdminClient.newDatabaseBuilder(dbId).disableDropProtection().build(); OperationFuture operation = - databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest); + databaseAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); Database updatedDb = operation.get(5, TimeUnit.MINUTES); - assertFalse(updatedDb.getEnableDropProtection()); + assertFalse(updatedDb.isDropProtectionEnabled()); } } diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSampleIT.java similarity index 61% rename from samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSampleIT.java rename to samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSampleIT.java index 3ec3587288..ce657575f0 100644 --- a/samples/snippets/src/test/java/com/example/spanner/admin/generated/UpdateDatabaseWithDefaultLeaderSampleIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/admin/archived/UpdateDatabaseWithDefaultLeaderSampleIT.java @@ -14,40 +14,45 @@ * limitations under the License. */ -package com.example.spanner.admin.generated; +package com.example.spanner.admin.archived; import static org.junit.Assert.assertTrue; import com.example.spanner.SampleRunner; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.example.spanner.SampleTestBase; +import com.example.spanner.admin.archived.UpdateDatabaseWithDefaultLeaderSample; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.InstanceConfigId; +import java.util.Collections; import java.util.concurrent.TimeUnit; import org.junit.Test; -public class UpdateDatabaseWithDefaultLeaderSampleIT extends SampleTestBaseV2 { +public class UpdateDatabaseWithDefaultLeaderSampleIT extends SampleTestBase { @Test public void testUpdateDatabaseWithDefaultLeader() throws Exception { // Create database final String databaseId = idGenerator.generateDatabaseId(); final Database createdDatabase = databaseAdminClient - .createDatabaseAsync(getInstanceName(projectId, multiRegionalInstanceId), - "CREATE DATABASE `" + databaseId + "`") + .createDatabase(multiRegionalInstanceId, databaseId, Collections.emptyList()) .get(5, TimeUnit.MINUTES); final String defaultLeader = createdDatabase.getDefaultLeader(); // Finds a possible new leader option - final String instanceConfigId = - instanceAdminClient.getInstance(getInstanceName(projectId, multiRegionalInstanceId)) - .getConfig(); - final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); - final String newLeader = - config.getLeaderOptionsList().stream() - .filter(leader -> !leader.equals(defaultLeader)) - .findFirst().orElseThrow(() -> - new RuntimeException("Expected to find a leader option different than " - + defaultLeader) - ); + final InstanceConfigId instanceConfigId = instanceAdminClient + .getInstance(multiRegionalInstanceId) + .getInstanceConfigId(); + final InstanceConfig config = instanceAdminClient + .getInstanceConfig(instanceConfigId.getInstanceConfig()); + final String newLeader = config + .getLeaderOptions() + .stream() + .filter(leader -> !leader.equals(defaultLeader)) + .findFirst() + .orElseThrow(() -> + new RuntimeException("Expected to find a leader option different than " + defaultLeader) + ); // Runs sample final String out = SampleRunner.runSample(() -> UpdateDatabaseWithDefaultLeaderSample diff --git a/versions.txt b/versions.txt index debc27fc48..42f18614e5 100644 --- a/versions.txt +++ b/versions.txt @@ -1,13 +1,13 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.60.1:6.60.1 -proto-google-cloud-spanner-v1:6.60.1:6.60.1 -proto-google-cloud-spanner-admin-database-v1:6.60.1:6.60.1 -grpc-google-cloud-spanner-v1:6.60.1:6.60.1 -grpc-google-cloud-spanner-admin-instance-v1:6.60.1:6.60.1 -grpc-google-cloud-spanner-admin-database-v1:6.60.1:6.60.1 -google-cloud-spanner:6.60.1:6.60.1 -google-cloud-spanner-executor:6.60.1:6.60.1 -proto-google-cloud-spanner-executor-v1:6.60.1:6.60.1 -grpc-google-cloud-spanner-executor-v1:6.60.1:6.60.1 +proto-google-cloud-spanner-admin-instance-v1:6.61.0:6.61.0 +proto-google-cloud-spanner-v1:6.61.0:6.61.0 +proto-google-cloud-spanner-admin-database-v1:6.61.0:6.61.0 +grpc-google-cloud-spanner-v1:6.61.0:6.61.0 +grpc-google-cloud-spanner-admin-instance-v1:6.61.0:6.61.0 +grpc-google-cloud-spanner-admin-database-v1:6.61.0:6.61.0 +google-cloud-spanner:6.61.0:6.61.0 +google-cloud-spanner-executor:6.61.0:6.61.0 +proto-google-cloud-spanner-executor-v1:6.61.0:6.61.0 +grpc-google-cloud-spanner-executor-v1:6.61.0:6.61.0