diff --git a/.github/readme/synth.metadata/synth.metadata b/.github/readme/synth.metadata/synth.metadata index c23ee933f30..dfef9855f54 100644 --- a/.github/readme/synth.metadata/synth.metadata +++ b/.github/readme/synth.metadata/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/java-spanner.git", - "sha": "5f3a67ad3a5213fa9fca977ebe4abb50ccd3f593" + "sha": "3f5351df6d13bd3bc3639e8a75e24ae924a4cfec" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "56ddc68f36b32341e9f22c2c59b4ce6aa3ba635f" + "sha": "b32c87d2c087fb2a841acc623ab540105de821af" } } ] diff --git a/.github/release-please.yml b/.github/release-please.yml index dce2c845092..cfb9fd5c507 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -1,2 +1,6 @@ releaseType: java-yoshi -bumpMinorPreMajor: true \ No newline at end of file +bumpMinorPreMajor: true +branches: +- branch: 3.1.x + releaseType: java-yoshi + bumpMinorPreMajor: true diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 6bddd18eac9..9eee4819794 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -39,6 +39,33 @@ branchProtectionRules: - "units (11)" - "Kokoro - Test: Integration" - "cla/google" + +# Identifies the protection rule pattern. Name of the branch to be protected. +# Defaults to `master` +- pattern: 3.1.x + # Can admins overwrite branch protection. + # Defaults to `true` + isAdminEnforced: true + # Number of approving reviews required to update matching branches. + # Defaults to `1` + requiredApprovingReviewCount: 1 + # Are reviews from code owners required to update matching branches. + # Defaults to `false` + requiresCodeOwnerReviews: true + # Require up to date branches + requiresStrictStatusChecks: false + # List of required status check contexts that must pass for commits to be accepted to matching branches. + requiredStatusCheckContexts: + - "dependencies (8)" + - "dependencies (11)" + - "linkage-monitor" + - "lint" + - "clirr" + - "units (7)" + - "units (8)" + - "units (11)" + - "Kokoro - Test: Integration" + - "cla/google" # List of explicit permissions to add (additive only) permissionRules: - team: yoshi-admins diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml index 2b6cdbc976c..7c8816a7d7b 100644 --- a/.github/workflows/auto-release.yaml +++ b/.github/workflows/auto-release.yaml @@ -4,7 +4,7 @@ name: auto-release jobs: approve: runs-on: ubuntu-latest - if: contains(github.head_ref, 'release-v') + if: contains(github.head_ref, 'release-please') steps: - uses: actions/github-script@v3 with: @@ -16,8 +16,8 @@ jobs: return; } - // only approve PRs like "chore: release " - if ( !context.payload.pull_request.title.startsWith("chore: release") ) { + // only approve PRs like "chore(master): release " + if ( !context.payload.pull_request.title.startsWith("chore(master): release") ) { return; } diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 68302207566..def8b3a2c84 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,7 +54,11 @@ jobs: with: java-version: 8 - run: java -version - - run: .kokoro/linkage-monitor.sh + - name: Install artifacts to local Maven repository + run: .kokoro/build.sh + shell: bash + - name: Validate any conflicts with regard to com.google.cloud:libraries-bom (latest release) + uses: GoogleCloudPlatform/cloud-opensource-java/linkage-monitor@v1-linkagemonitor lint: runs-on: ubuntu-latest steps: diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 8280d10ad67..4572da72d57 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -110,8 +110,8 @@ bash .kokoro/coerce_logs.sh if [[ "${ENABLE_BUILD_COP}" == "true" ]] then - chmod +x ${KOKORO_GFILE_DIR}/linux_amd64/buildcop - ${KOKORO_GFILE_DIR}/linux_amd64/buildcop -repo=googleapis/java-spanner + chmod +x ${KOKORO_GFILE_DIR}/linux_amd64/flakybot + ${KOKORO_GFILE_DIR}/linux_amd64/flakybot -repo=googleapis/java-spanner fi echo "exiting with ${RETURN_CODE}" diff --git a/.kokoro/linkage-monitor.sh b/.kokoro/linkage-monitor.sh deleted file mode 100755 index 759ab4e2c22..00000000000 --- a/.kokoro/linkage-monitor.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# Copyright 2019 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. - -set -eo pipefail -# Display commands being run. -set -x - -## Get the directory of the build script -scriptDir=$(realpath $(dirname "${BASH_SOURCE[0]}")) -## cd to the parent directory, i.e. the root of the git repo -cd ${scriptDir}/.. - -# include common functions -source ${scriptDir}/common.sh - -# Print out Java version -java -version -echo ${JOB_TYPE} - -# attempt to install 3 times with exponential backoff (starting with 10 seconds) -retry_with_backoff 3 10 \ - mvn install -B -V \ - -DskipTests=true \ - -Dclirr.skip=true \ - -Denforcer.skip=true \ - -Dmaven.javadoc.skip=true \ - -Dgcloud.download.skip=true - -# Kokoro job cloud-opensource-java/ubuntu/linkage-monitor-gcs creates this JAR -JAR=linkage-monitor-latest-all-deps.jar -curl -v -O "https://storage.googleapis.com/cloud-opensource-java-linkage-monitor/${JAR}" - -# Fails if there's new linkage errors compared with baseline -java -jar ${JAR} com.google.cloud:libraries-bom diff --git a/.kokoro/release/publish_javadoc.cfg b/.kokoro/release/publish_javadoc.cfg index 2a5ec04f9b9..cbde2338f3d 100644 --- a/.kokoro/release/publish_javadoc.cfg +++ b/.kokoro/release/publish_javadoc.cfg @@ -26,4 +26,4 @@ before_action { keyname: "docuploader_service_account" } } -} +} \ No newline at end of file diff --git a/.kokoro/release/publish_javadoc11.cfg b/.kokoro/release/publish_javadoc11.cfg new file mode 100644 index 00000000000..02cd9b1ccff --- /dev/null +++ b/.kokoro/release/publish_javadoc11.cfg @@ -0,0 +1,30 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "STAGING_BUCKET_V2" + value: "docs-staging-v2" + # Production will be at: docs-staging-v2 +} + +# Configure the docker image for kokoro-trampoline +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java11" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/java-spanner/.kokoro/release/publish_javadoc11.sh" +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "docuploader_service_account" + } + } +} + +# Downloads docfx doclet resource. This will be in ${KOKORO_GFILE_DIR}/ +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/docfx" diff --git a/.kokoro/release/publish_javadoc11.sh b/.kokoro/release/publish_javadoc11.sh new file mode 100755 index 00000000000..63af49cf0da --- /dev/null +++ b/.kokoro/release/publish_javadoc11.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2021 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. + +set -eo pipefail + +if [[ -z "${CREDENTIALS}" ]]; then + CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account +fi + +if [[ -z "${STAGING_BUCKET_V2}" ]]; then + echo "Need to set STAGING_BUCKET_V2 environment variable" + exit 1 +fi + +# work from the git root directory +pushd $(dirname "$0")/../../ + +# install docuploader package +python3 -m pip install gcp-docuploader + +# compile all packages +mvn clean install -B -q -DskipTests=true + +export NAME=google-cloud-spanner +export VERSION=$(grep ${NAME}: versions.txt | cut -d: -f3) + +# V3 generates docfx yml from javadoc +# generate yml +mvn clean site -B -q -P docFX + +pushd target/docfx-yml + +# create metadata +python3 -m docuploader create-metadata \ + --name ${NAME} \ + --version ${VERSION} \ + --language java + +# upload yml +python3 -m docuploader upload . \ + --credentials ${CREDENTIALS} \ + --staging-bucket ${STAGING_BUCKET_V2} \ + --destination-prefix docfx- diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df923a9d96..aaa57c3d4ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## [4.0.0](https://www.github.com/googleapis/java-spanner/compare/v3.3.2...v4.0.0) (2021-02-17) + + +### ⚠ BREAKING CHANGES + +* Point In Time Recovery (PITR) (#452) +* add support for CommitStats (#544) + +### Features + +* add option for returning Spanner commit stats ([#817](https://www.github.com/googleapis/java-spanner/issues/817)) ([80d3585](https://www.github.com/googleapis/java-spanner/commit/80d3585870b81949ec641291e5a88fe391f78e27)) +* add support for CommitStats ([#544](https://www.github.com/googleapis/java-spanner/issues/544)) ([44aa384](https://www.github.com/googleapis/java-spanner/commit/44aa384429056dd6c6563351c43fe7dcac451008)) +* allow session pool settings in connection url ([#821](https://www.github.com/googleapis/java-spanner/issues/821)) ([e1e9152](https://www.github.com/googleapis/java-spanner/commit/e1e915289755e5f46ba07569d85afda5df5e3f0d)) +* generate sample code in the Java microgenerator ([#859](https://www.github.com/googleapis/java-spanner/issues/859)) ([7cdfb82](https://www.github.com/googleapis/java-spanner/commit/7cdfb82b40487600547d0bad92119508161ca689)) +* Point In Time Recovery (PITR) ([#452](https://www.github.com/googleapis/java-spanner/issues/452)) ([ab14a5e](https://www.github.com/googleapis/java-spanner/commit/ab14a5ec2dc2b7e2141305b5326f436eb6eee76f)) + + +### Bug Fixes + +* allows user-agent header with header provider ([#871](https://www.github.com/googleapis/java-spanner/issues/871)) ([3de7e2a](https://www.github.com/googleapis/java-spanner/commit/3de7e2a91349cac5d79a32d2cda7ca727140f0bf)) +* make compiled statements immutable ([#843](https://www.github.com/googleapis/java-spanner/issues/843)) ([118d1b3](https://www.github.com/googleapis/java-spanner/commit/118d1b31f5f7771023766fd72a8229db80f1f5a2)) + + +### Dependencies + +* update dependency com.google.cloud:google-cloud-monitoring to v2.0.12 ([#854](https://www.github.com/googleapis/java-spanner/issues/854)) ([58cebd8](https://www.github.com/googleapis/java-spanner/commit/58cebd85a9d82bd1526b9eae98892181f1a022f1)) +* update dependency com.google.cloud:google-cloud-trace to v1.2.11 ([#825](https://www.github.com/googleapis/java-spanner/issues/825)) ([49c8c5d](https://www.github.com/googleapis/java-spanner/commit/49c8c5d241803565fa9ff96ba55f3eb00ed5b85e)) + + +### Documentation + +* libraries-bom 16.4.0 ([#867](https://www.github.com/googleapis/java-spanner/issues/867)) ([5af3673](https://www.github.com/googleapis/java-spanner/commit/5af36739532037360dfd504a4a0988562550526c)) + ### [3.3.2](https://www.github.com/googleapis/java-spanner/compare/v3.3.1...v3.3.2) (2021-01-18) @@ -15,6 +48,15 @@ ### [3.3.1](https://www.github.com/googleapis/java-spanner/compare/v3.3.0...v3.3.1) (2021-01-14) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Bug Fixes @@ -31,6 +73,15 @@ ## [3.3.0](https://www.github.com/googleapis/java-spanner/compare/v3.2.1...v3.3.0) (2021-01-07) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Features @@ -43,6 +94,15 @@ ### [3.2.1](https://www.github.com/googleapis/java-spanner/compare/v3.2.0...v3.2.1) (2021-01-06) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Bug Fixes @@ -63,6 +123,15 @@ ## [3.2.0](https://www.github.com/googleapis/java-spanner/compare/v3.1.3...v3.2.0) (2020-12-17) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Features @@ -87,6 +156,15 @@ ### [3.1.3](https://www.github.com/googleapis/java-spanner/compare/v3.1.2...v3.1.3) (2020-12-14) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Dependencies @@ -94,6 +172,15 @@ ### [3.1.2](https://www.github.com/googleapis/java-spanner/compare/v3.1.1...v3.1.2) (2020-12-14) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Dependencies @@ -101,6 +188,15 @@ ### [3.1.1](https://www.github.com/googleapis/java-spanner/compare/v3.1.0...v3.1.1) (2020-12-10) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Dependencies @@ -108,6 +204,15 @@ ## [3.1.0](https://www.github.com/googleapis/java-spanner/compare/v3.0.5...v3.1.0) (2020-12-10) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Features @@ -130,6 +235,15 @@ ### [3.0.5](https://www.github.com/googleapis/java-spanner/compare/v3.0.4...v3.0.5) (2020-11-19) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Bug Fixes @@ -144,6 +258,15 @@ ### [3.0.4](https://www.github.com/googleapis/java-spanner/compare/v3.0.3...v3.0.4) (2020-11-17) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Reverts @@ -151,6 +274,15 @@ ### [3.0.3](https://www.github.com/googleapis/java-spanner/compare/v3.0.2...v3.0.3) (2020-11-16) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Dependencies @@ -158,6 +290,15 @@ ### [3.0.2](https://www.github.com/googleapis/java-spanner/compare/v3.0.1...v3.0.2) (2020-11-13) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Bug Fixes @@ -181,6 +322,15 @@ ### [3.0.1](https://www.github.com/googleapis/java-spanner/compare/v3.0.0...v3.0.1) (2020-10-28) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### Bug Fixes @@ -196,6 +346,15 @@ ## [3.0.0](https://www.github.com/googleapis/java-spanner/compare/v2.0.2...v3.0.0) (2020-10-23) +### ⚠ IMPORTANT: Known issue with this version of the client + +Since [v3.0.0](https://github.com/googleapis/java-spanner/releases/tag/v3.0.0), transactions can get stuck if the Spanner backend returns a retryable error when consuming the first record of a read / query in a transaction. + +A [fix](https://github.com/googleapis/java-spanner/pull/807) is submitted and available in version [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) + +**Please use [v3.3.2](https://github.com/googleapis/java-spanner/releases/tag/v3.3.2) instead of this version.** + +Apologies for the inconvenience. ### ⚠ BREAKING CHANGES diff --git a/LICENSE b/LICENSE index d6456956733..261eeb9e9f8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.md b/README.md index 4228838d64e..f35503e755a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file com.google.cloud libraries-bom - 16.3.0 + 16.4.0 pom import @@ -38,25 +38,25 @@ If you are using Maven without BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 3.3.1 + 3.3.2 ``` If you are using Gradle 5.x or later, add this to your dependencies ```Groovy -implementation platform('com.google.cloud:libraries-bom:16.3.0') +implementation platform('com.google.cloud:libraries-bom:16.4.0') compile 'com.google.cloud:google-cloud-spanner' ``` If you are using Gradle without BOM, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-spanner:3.3.1' +compile 'com.google.cloud:google-cloud-spanner:3.3.2' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "3.3.1" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "3.3.2" ``` ## Authentication diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index 2e6d7695172..14a3963a578 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 - 3.3.2 + 4.0.0 pom com.google.cloud google-cloud-shared-config - 0.9.4 + 0.10.0 Google Cloud Spanner BOM @@ -64,43 +64,43 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 3.3.2 + 4.0.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 3.3.2 + 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 3.3.2 + 4.0.0 com.google.cloud google-cloud-spanner - 3.3.2 + 4.0.0 com.google.cloud google-cloud-spanner test-jar - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 3.3.2 + 4.0.0 diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 8dd5d9dad6d..c72dc8c669c 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -453,4 +453,38 @@ com/google/cloud/spanner/TransactionContext com.google.api.core.ApiFuture executeUpdateAsync(com.google.cloud.spanner.Statement) + + + + 7012 + com/google/cloud/spanner/AsyncTransactionManager + com.google.api.core.ApiFuture getCommitResponse() + + + 7012 + com/google/cloud/spanner/AsyncRunner + com.google.api.core.ApiFuture getCommitResponse() + + + 7012 + com/google/cloud/spanner/TransactionManager + com.google.cloud.spanner.CommitResponse getCommitResponse() + + + 7012 + com/google/cloud/spanner/TransactionRunner + com.google.cloud.spanner.CommitResponse getCommitResponse() + + + + + 7013 + com/google/cloud/spanner/BackupInfo$Builder + com.google.cloud.spanner.BackupInfo$Builder setVersionTime(com.google.cloud.Timestamp) + + + 7012 + com/google/cloud/spanner/DatabaseAdminClient + com.google.api.gax.longrunning.OperationFuture createBackup(com.google.cloud.spanner.Backup) + diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index f27a5be556d..323dc2a364a 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 - 3.3.2 + 4.0.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 3.3.2 + 4.0.0 google-cloud-spanner 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 1d26adc757b..93f7e6cb0ce 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 @@ -786,7 +786,7 @@ static class GrpcStreamIterator extends AbstractIterator private final Statement statement; private SpannerRpc.StreamingCall call; - private boolean withBeginTransaction; + private volatile boolean withBeginTransaction; private SpannerException error; @VisibleForTesting @@ -889,12 +889,6 @@ public void onError(SpannerException e) { error = e; addToStream(END_OF_STREAM); } - - // Visible only for testing. - @VisibleForTesting - void setCall(SpannerRpc.StreamingCall call, boolean withBeginTransaction) { - GrpcStreamIterator.this.setCall(call, withBeginTransaction); - } } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunner.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunner.java index 3cae49e65b4..c9dec98d55d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunner.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunner.java @@ -56,4 +56,10 @@ interface AsyncWork { * {@link ExecutionException} if the transaction did not commit. */ ApiFuture getCommitTimestamp(); + + /** + * Returns the {@link CommitResponse} of this transaction. {@link ApiFuture#get()} throws an + * {@link ExecutionException} if the transaction did not commit. + */ + ApiFuture getCommitResponse(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunnerImpl.java index 5b83402919e..7982f0d2822 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunnerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunnerImpl.java @@ -16,23 +16,31 @@ package com.google.cloud.spanner; +import static com.google.common.base.Preconditions.checkState; + +import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.api.core.SettableApiFuture; import com.google.cloud.Timestamp; import com.google.cloud.spanner.TransactionRunner.TransactionCallable; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; class AsyncRunnerImpl implements AsyncRunner { private final TransactionRunnerImpl delegate; - private final SettableApiFuture commitTimestamp = SettableApiFuture.create(); + private SettableApiFuture commitResponse; AsyncRunnerImpl(TransactionRunnerImpl delegate) { - this.delegate = delegate; + this.delegate = Preconditions.checkNotNull(delegate); } @Override public ApiFuture runAsync(final AsyncWork work, Executor executor) { + Preconditions.checkState(commitResponse == null, "runAsync() can only be called once"); + commitResponse = SettableApiFuture.create(); final SettableApiFuture res = SettableApiFuture.create(); executor.execute( new Runnable() { @@ -43,7 +51,7 @@ public void run() { } catch (Throwable t) { res.setException(t); } finally { - setCommitTimestamp(); + setCommitResponse(); } } }); @@ -66,16 +74,30 @@ public R run(TransactionContext transaction) throws Exception { }); } - private void setCommitTimestamp() { + private void setCommitResponse() { try { - commitTimestamp.set(delegate.getCommitTimestamp()); + commitResponse.set(delegate.getCommitResponse()); } catch (Throwable t) { - commitTimestamp.setException(t); + commitResponse.setException(t); } } @Override public ApiFuture getCommitTimestamp() { - return commitTimestamp; + checkState(commitResponse != null, "runAsync() has not yet been called"); + return ApiFutures.transform( + commitResponse, + new ApiFunction() { + @Override + public Timestamp apply(CommitResponse input) { + return input.getCommitTimestamp(); + } + }, + MoreExecutors.directExecutor()); + } + + public ApiFuture getCommitResponse() { + checkState(commitResponse != null, "runAsync() has not yet been called"); + return commitResponse; } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java index 02d4a9dbd23..a4741dd3293 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java @@ -191,6 +191,9 @@ public interface AsyncTransactionFunction { /** Returns the state of the transaction. */ TransactionState getState(); + /** Returns the {@link CommitResponse} of this transaction. */ + ApiFuture getCommitResponse(); + /** * Closes the manager. If there is an active transaction, it will be rolled back. Underlying * session will be released back to the session pool. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java index 8dab813015e..2ec914c24aa 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner; import com.google.api.core.ApiAsyncFunction; +import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutureCallback; import com.google.api.core.ApiFutures; @@ -45,7 +46,7 @@ final class AsyncTransactionManagerImpl private TransactionRunnerImpl.TransactionContextImpl txn; private TransactionState txnState; - private final SettableApiFuture commitTimestamp = SettableApiFuture.create(); + private final SettableApiFuture commitResponse = SettableApiFuture.create(); AsyncTransactionManagerImpl(SessionImpl session, Span span, TransactionOption... options) { this.session = session; @@ -132,29 +133,37 @@ public ApiFuture commitAsync() { SpannerExceptionFactory.newSpannerException( ErrorCode.ABORTED, "Transaction already aborted")); } - ApiFuture res = txn.commitAsync(); + ApiFuture commitResponseFuture = txn.commitAsync(); txnState = TransactionState.COMMITTED; ApiFutures.addCallback( - res, - new ApiFutureCallback() { + commitResponseFuture, + new ApiFutureCallback() { @Override public void onFailure(Throwable t) { if (t instanceof AbortedException) { txnState = TransactionState.ABORTED; } else { txnState = TransactionState.COMMIT_FAILED; - commitTimestamp.setException(t); + commitResponse.setException(t); } } @Override - public void onSuccess(Timestamp result) { - commitTimestamp.set(result); + public void onSuccess(CommitResponse result) { + commitResponse.set(result); + } + }, + MoreExecutors.directExecutor()); + return ApiFutures.transform( + commitResponseFuture, + new ApiFunction() { + @Override + public Timestamp apply(CommitResponse input) { + return input.getCommitTimestamp(); } }, MoreExecutors.directExecutor()); - return res; } @Override @@ -187,6 +196,11 @@ public TransactionState getState() { return txnState; } + @Override + public ApiFuture getCommitResponse() { + return commitResponse; + } + @Override public void invalidate() { if (txnState == TransactionState.STARTED || txnState == null) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java index d49e9f7f50b..a0052cd381a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java @@ -65,7 +65,7 @@ public OperationFuture create() { getExpireTime() != null, "Cannot create a backup without an expire time"); Preconditions.checkState( getDatabase() != null, "Cannot create a backup without a source database"); - return dbClient.createBackup(instance(), backup(), sourceDatabase(), getExpireTime()); + return dbClient.createBackup(this); } /** @@ -182,6 +182,7 @@ static Backup fromProto( .setState(fromProtoState(proto.getState())) .setSize(proto.getSizeBytes()) .setExpireTime(Timestamp.fromProto(proto.getExpireTime())) + .setVersionTime(Timestamp.fromProto(proto.getVersionTime())) .setDatabase(DatabaseId.of(proto.getDatabase())) .setProto(proto) .build(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java index 5968bcdc213..199e6ae2ae4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java @@ -18,6 +18,7 @@ import com.google.api.client.util.Preconditions; import com.google.cloud.Timestamp; +import com.google.spanner.admin.database.v1.Database; import java.util.Objects; import javax.annotation.Nullable; @@ -40,6 +41,17 @@ public abstract static class Builder { */ public abstract Builder setExpireTime(Timestamp expireTime); + /** + * Optional for creating a new backup. + * + *

Specifies the timestamp to have an externally consistent copy of the database. If no + * version time is specified, it will be automatically set to the backup create time. + * + *

The version time can be as far in the past as specified by the database earliest version + * time (see {@link Database#getEarliestVersionTime()}). + */ + public abstract Builder setVersionTime(Timestamp versionTime); + /** * Required for creating a new backup. * @@ -55,6 +67,7 @@ abstract static class BuilderImpl extends Builder { protected final BackupId id; private State state = State.UNSPECIFIED; private Timestamp expireTime; + private Timestamp versionTime; private DatabaseId database; private long size; private com.google.spanner.admin.database.v1.Backup proto; @@ -67,6 +80,7 @@ abstract static class BuilderImpl extends Builder { this.id = other.id; this.state = other.state; this.expireTime = other.expireTime; + this.versionTime = other.versionTime; this.database = other.database; this.size = other.size; this.proto = other.proto; @@ -84,6 +98,12 @@ public Builder setExpireTime(Timestamp expireTime) { return this; } + @Override + public Builder setVersionTime(Timestamp versionTime) { + this.versionTime = versionTime; + return this; + } + @Override public Builder setDatabase(DatabaseId database) { Preconditions.checkArgument( @@ -119,6 +139,7 @@ public enum State { private final BackupId id; private final State state; private final Timestamp expireTime; + private final Timestamp versionTime; private final DatabaseId database; private final long size; private final com.google.spanner.admin.database.v1.Backup proto; @@ -128,6 +149,7 @@ public enum State { this.state = builder.state; this.size = builder.size; this.expireTime = builder.expireTime; + this.versionTime = builder.versionTime; this.database = builder.database; this.proto = builder.proto; } @@ -157,6 +179,11 @@ public Timestamp getExpireTime() { return expireTime; } + /** Returns the version time of the backup. */ + public Timestamp getVersionTime() { + return versionTime; + } + /** Returns the id of the database that was used to create the backup. */ public DatabaseId getDatabase() { return database; @@ -180,17 +207,19 @@ public boolean equals(Object o) { && state == that.state && size == that.size && Objects.equals(expireTime, that.expireTime) + && Objects.equals(versionTime, that.versionTime) && Objects.equals(database, that.database); } @Override public int hashCode() { - return Objects.hash(id, state, size, expireTime, database); + return Objects.hash(id, state, size, expireTime, versionTime, database); } @Override public String toString() { return String.format( - "Backup[%s, %s, %d, %s, %s]", id.getName(), state, size, expireTime, database); + "Backup[%s, %s, %d, %s, %s, %s]", + id.getName(), state, size, expireTime, versionTime, database); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitResponse.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitResponse.java index dd5534d7c35..00505cf8d1f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitResponse.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitResponse.java @@ -17,20 +17,38 @@ package com.google.cloud.spanner; import com.google.cloud.Timestamp; +import com.google.common.base.Preconditions; import java.util.Objects; /** Represents a response from a commit operation. */ public class CommitResponse { - private final Timestamp commitTimestamp; + private final com.google.spanner.v1.CommitResponse proto; public CommitResponse(Timestamp commitTimestamp) { - this.commitTimestamp = commitTimestamp; + this.proto = + com.google.spanner.v1.CommitResponse.newBuilder() + .setCommitTimestamp(commitTimestamp.toProto()) + .build(); } - /** Returns a {@link Timestamp} representing the commit time of the write operation. */ + CommitResponse(com.google.spanner.v1.CommitResponse proto) { + this.proto = Preconditions.checkNotNull(proto); + } + + /** Returns a {@link Timestamp} representing the commit time of the transaction. */ public Timestamp getCommitTimestamp() { - return commitTimestamp; + return Timestamp.fromProto(proto.getCommitTimestamp()); + } + + /** + * Commit statistics are returned by a read/write transaction if specifically requested by passing + * in {@link Options#commitStats()} to the transaction. + */ + public CommitStats getCommitStats() { + Preconditions.checkState( + proto.hasCommitStats(), "The CommitResponse does not contain any commit statistics."); + return CommitStats.fromProto(proto.getCommitStats()); } @Override @@ -42,11 +60,11 @@ public boolean equals(Object o) { return false; } CommitResponse that = (CommitResponse) o; - return Objects.equals(commitTimestamp, that.commitTimestamp); + return Objects.equals(proto, that.proto); } @Override public int hashCode() { - return Objects.hash(commitTimestamp); + return Objects.hash(proto); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitStats.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitStats.java new file mode 100644 index 00000000000..eaf1a78819f --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CommitStats.java @@ -0,0 +1,54 @@ +/* + * 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.google.cloud.spanner; + +import com.google.common.base.Preconditions; + +/** + * Commit statistics are returned by a read/write transaction if specifically requested by passing + * in {@link Options#commitStats()} to the transaction. + */ +public class CommitStats { + private final long mutationCount; + + private CommitStats(long mutationCount) { + this.mutationCount = mutationCount; + } + + static CommitStats fromProto(com.google.spanner.v1.CommitResponse.CommitStats proto) { + Preconditions.checkNotNull(proto); + return new CommitStats(proto.getMutationCount()); + } + + /** + * The number of mutations that were executed by the transaction. Insert and update operations + * count with the multiplicity of the number of columns they affect. For example, inserting a new + * record may count as five mutations, if values are inserted into five columns. Delete and delete + * range operations count as one mutation regardless of the number of columns affected. Deleting a + * row from a parent table that has the ON DELETE CASCADE annotation is also counted as one + * mutation regardless of the number of interleaved child rows present. The exception to this is + * if there are secondary indexes defined on rows being deleted, then the changes to the secondary + * indexes are counted individually. For example, if a table has 2 secondary indexes, deleting a + * range of rows in the table counts as 1 mutation for the table, plus 2 mutations for each row + * that is deleted because the rows in the secondary index might be scattered over the key-space, + * making it impossible for Cloud Spanner to call a single delete range operation on the secondary + * indexes. Secondary indexes include the foreign keys backing indexes. + */ + public long getMutationCount() { + return mutationCount; + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 89ee6e875ac..a442ad2399c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -118,8 +118,14 @@ public OperationFuture backup(Backup backup) { Preconditions.checkArgument( backup.getInstanceId().equals(getId().getInstanceId()), "The instance of the backup must be equal to the instance of this database."); + return dbClient.createBackup( - instance(), backup.getId().getBackup(), database(), backup.getExpireTime()); + dbClient + .newBackupBuilder(backup.getId()) + .setDatabase(getId()) + .setExpireTime(backup.getExpireTime()) + .setVersionTime(backup.getVersionTime()) + .build()); } /** @@ -177,6 +183,8 @@ static Database fromProto( .setState(fromProtoState(proto.getState())) .setCreateTime(Timestamp.fromProto(proto.getCreateTime())) .setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo())) + .setVersionRetentionPeriod(proto.getVersionRetentionPeriod()) + .setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime())) .setProto(proto) .build(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java index e7a8ed67871..eae1a3cdf4e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java @@ -101,6 +101,32 @@ OperationFuture createBackup( String sourceInstanceId, String backupId, String databaseId, Timestamp expireTime) throws SpannerException; + /** + * Creates a new backup from a database in a Cloud Spanner instance. + * + *

Example to create a backup. + * + *

{@code
+   * BackupId backupId     = BackupId.of("project", "instance", "backup-id");
+   * DatabaseId databaseId = DatabaseId.of("project", "instance", "database-id");
+   * Timestamp expireTime  = Timestamp.ofTimeMicroseconds(expireTimeMicros);
+   * Timestamp versionTime = Timestamp.ofTimeMicroseconds(versionTimeMicros);
+   *
+   * Backup backupToCreate = dbAdminClient
+   *     .newBackupBuilder(backupId)
+   *     .setDatabase(databaseId)
+   *     .setExpireTime(expireTime)
+   *     .setVersionTime(versionTime)
+   *     .build();
+   *
+   * OperationFuture op = dbAdminClient.createBackup(backupToCreate);
+   * Backup createdBackup = op.get();
+   * }
+ * + * @param backup the backup to be created + */ + OperationFuture createBackup(Backup backup) throws SpannerException; + /** * Restore a database from a backup. The database that is restored will be created and may not * already exist. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java index 28de150caec..a5eed214a4e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java @@ -114,16 +114,31 @@ public Database apply(Exception e) { public OperationFuture createBackup( String instanceId, String backupId, String databaseId, Timestamp expireTime) throws SpannerException { - com.google.spanner.admin.database.v1.Backup backup = + final Backup backup = + newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) + .setDatabase(DatabaseId.of(projectId, instanceId, databaseId)) + .setExpireTime(expireTime) + .build(); + return createBackup(backup); + } + + @Override + public OperationFuture createBackup(final Backup backup) { + final String instanceId = backup.getInstanceId().getInstance(); + final String databaseId = backup.getDatabase().getDatabase(); + final String backupId = backup.getId().getBackup(); + final com.google.spanner.admin.database.v1.Backup.Builder backupBuilder = com.google.spanner.admin.database.v1.Backup.newBuilder() .setDatabase(getDatabaseName(instanceId, databaseId)) - .setExpireTime(expireTime.toProto()) - .build(); - String instanceName = getInstanceName(instanceId); - OperationFuture - rawOperationFuture = rpc.createBackup(instanceName, backupId, backup); + .setExpireTime(backup.getExpireTime().toProto()); + if (backup.getVersionTime() != null) { + backupBuilder.setVersionTime(backup.getVersionTime().toProto()); + } + final String instanceName = getInstanceName(instanceId); + final OperationFuture + rawOperationFuture = rpc.createBackup(instanceName, backupId, backupBuilder.build()); - return new OperationFutureImpl( + return new OperationFutureImpl<>( rawOperationFuture.getPollingFuture(), rawOperationFuture.getInitialFuture(), new ApiFunction() { @@ -137,6 +152,7 @@ public Backup apply(OperationSnapshot snapshot) { com.google.spanner.admin.database.v1.Backup.newBuilder(proto) .setName(proto.getName()) .setExpireTime(proto.getExpireTime()) + .setVersionTime(proto.getVersionTime()) .setState(proto.getState()) .build(), DatabaseAdminClientImpl.this); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 5b7faa324e6..5ba9f0aa765 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -30,6 +30,10 @@ public abstract static class Builder { abstract Builder setRestoreInfo(RestoreInfo restoreInfo); + abstract Builder setVersionRetentionPeriod(String versionRetentionPeriod); + + abstract Builder setEarliestVersionTime(Timestamp earliestVersionTime); + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -41,6 +45,8 @@ abstract static class BuilderImpl extends Builder { private State state = State.UNSPECIFIED; private Timestamp createTime; private RestoreInfo restoreInfo; + private String versionRetentionPeriod; + private Timestamp earliestVersionTime; private com.google.spanner.admin.database.v1.Database proto; BuilderImpl(DatabaseId id) { @@ -52,6 +58,8 @@ abstract static class BuilderImpl extends Builder { this.state = other.state; this.createTime = other.createTime; this.restoreInfo = other.restoreInfo; + this.versionRetentionPeriod = other.versionRetentionPeriod; + this.earliestVersionTime = other.earliestVersionTime; this.proto = other.proto; } @@ -73,6 +81,18 @@ Builder setRestoreInfo(@Nullable RestoreInfo restoreInfo) { return this; } + @Override + Builder setVersionRetentionPeriod(String versionRetentionPeriod) { + this.versionRetentionPeriod = versionRetentionPeriod; + return this; + } + + @Override + Builder setEarliestVersionTime(Timestamp earliestVersionTime) { + this.earliestVersionTime = earliestVersionTime; + return this; + } + @Override Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) { this.proto = proto; @@ -96,6 +116,8 @@ public enum State { private final State state; private final Timestamp createTime; private final RestoreInfo restoreInfo; + private final String versionRetentionPeriod; + private final Timestamp earliestVersionTime; private final com.google.spanner.admin.database.v1.Database proto; public DatabaseInfo(DatabaseId id, State state) { @@ -103,6 +125,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.state = state; this.createTime = null; this.restoreInfo = null; + this.versionRetentionPeriod = null; + this.earliestVersionTime = null; this.proto = null; } @@ -111,6 +135,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.state = builder.state; this.createTime = builder.createTime; this.restoreInfo = builder.restoreInfo; + this.versionRetentionPeriod = builder.versionRetentionPeriod; + this.earliestVersionTime = builder.earliestVersionTime; this.proto = builder.proto; } @@ -129,6 +155,23 @@ public Timestamp getCreateTime() { return createTime; } + /** + * Returns the version retention period of the database. This is the period for which Cloud + * Spanner retains all versions of data for the database. For instance, if set to 3 days, Cloud + * Spanner will retain data versions that are up to 3 days old. + */ + public String getVersionRetentionPeriod() { + return versionRetentionPeriod; + } + + /** + * Returns the earliest version time of the database. This is the oldest timestamp that can be + * used to read old versions of the data. + */ + public Timestamp getEarliestVersionTime() { + return earliestVersionTime; + } + /** * Returns the {@link RestoreInfo} of the database if any is available, or null if no * {@link RestoreInfo} is available for this database. @@ -154,16 +197,21 @@ public boolean equals(Object o) { return id.equals(that.id) && state == that.state && Objects.equals(createTime, that.createTime) - && Objects.equals(restoreInfo, that.restoreInfo); + && Objects.equals(restoreInfo, that.restoreInfo) + && Objects.equals(versionRetentionPeriod, that.versionRetentionPeriod) + && Objects.equals(earliestVersionTime, that.earliestVersionTime); } @Override public int hashCode() { - return Objects.hash(id, state, createTime, restoreInfo); + return Objects.hash( + id, state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime); } @Override public String toString() { - return String.format("Database[%s, %s, %s, %s]", id.getName(), state, createTime, restoreInfo); + return String.format( + "Database[%s, %s, %s, %s, %s, %s]", + id.getName(), state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java index 26d3cd68322..38932bf8a80 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java @@ -46,6 +46,11 @@ public interface UpdateOption {} /** Marker interface to mark options applicable to list operations in admin API. */ public interface ListOption {} + /** Specifying this instructs the transaction to request {@link CommitStats} from the backend. */ + public static TransactionOption commitStats() { + return COMMIT_STATS_OPTION; + } + /** * Specifying this will cause the read to yield at most this many rows. This should be greater * than 0. @@ -116,6 +121,16 @@ public static ListOption filter(String filter) { return new FilterOption(filter); } + /** Option to request {@link CommitStats} for read/write transactions. */ + static final class CommitStatsOption extends InternalOption implements TransactionOption { + @Override + void appendToOptions(Options options) { + options.withCommitStats = true; + } + } + + static final CommitStatsOption COMMIT_STATS_OPTION = new CommitStatsOption(); + /** Option pertaining to flow control. */ static final class FlowControlOption extends InternalOption implements ReadAndQueryOption { final int prefetchChunks; @@ -143,6 +158,7 @@ void appendToOptions(Options options) { } } + private boolean withCommitStats; private Long limit; private Integer prefetchChunks; private Integer bufferRows; @@ -153,6 +169,10 @@ void appendToOptions(Options options) { // Construction is via factory methods below. private Options() {} + boolean withCommitStats() { + return withCommitStats; + } + boolean hasLimit() { return limit != null; } @@ -204,6 +224,9 @@ String filter() { @Override public String toString() { StringBuilder b = new StringBuilder(); + if (withCommitStats) { + b.append("withCommitStats: ").append(withCommitStats).append(' '); + } if (limit != null) { b.append("limit: ").append(limit).append(' '); } @@ -234,7 +257,8 @@ public boolean equals(Object o) { } Options that = (Options) o; - return (!hasLimit() && !that.hasLimit() + return Objects.equals(withCommitStats, that.withCommitStats) + && (!hasLimit() && !that.hasLimit() || hasLimit() && that.hasLimit() && Objects.equals(limit(), that.limit())) && (!hasPrefetchChunks() && !that.hasPrefetchChunks() || hasPrefetchChunks() @@ -253,6 +277,9 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = 31; + if (withCommitStats) { + result = 31 * result + 1231; + } if (limit != null) { result = 31 * result + limit.hashCode(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java index a20a6b7b248..51ec7b4d50d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java @@ -147,7 +147,7 @@ public Void run(TransactionContext ctx) { return null; } }); - return new CommitResponse(runner.getCommitTimestamp()); + return runner.getCommitResponse(); } @Override @@ -165,6 +165,8 @@ public CommitResponse writeAtLeastOnceWithOptions( final CommitRequest request = CommitRequest.newBuilder() .setSession(name) + .setReturnCommitStats( + Options.fromTransactionOptions(transactionOptions).withCommitStats()) .addAllMutations(mutationsProto) .setSingleUseTransaction( TransactionOptions.newBuilder() @@ -174,11 +176,7 @@ public CommitResponse writeAtLeastOnceWithOptions( try (Scope s = tracer.withSpan(span)) { com.google.spanner.v1.CommitResponse response = spanner.getRpc().commit(request, this.options); - Timestamp t = Timestamp.fromProto(response.getCommitTimestamp()); - return new CommitResponse(t); - } catch (IllegalArgumentException e) { - TraceUtil.setWithFailure(span, e); - throw newSpannerException(ErrorCode.INTERNAL, "Could not parse commit timestamp", e); + return new CommitResponse(response); } catch (RuntimeException e) { TraceUtil.setWithFailure(span, e); throw e; @@ -246,17 +244,12 @@ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) { @Override public TransactionRunner readWriteTransaction(TransactionOption... options) { - return setActive( - new TransactionRunnerImpl( - this, spanner.getRpc(), spanner.getDefaultPrefetchChunks(), options)); + return setActive(new TransactionRunnerImpl(this, options)); } @Override public AsyncRunner runAsync(TransactionOption... options) { - return new AsyncRunnerImpl( - setActive( - new TransactionRunnerImpl( - this, spanner.getRpc(), spanner.getDefaultPrefetchChunks(), options))); + return new AsyncRunnerImpl(setActive(new TransactionRunnerImpl(this, options))); } @Override @@ -350,6 +343,7 @@ public void run() { TransactionContextImpl newTransaction(Options options) { return TransactionContextImpl.newBuilder() .setSession(this) + .setOptions(options) .setTransactionId(readyTransactionId) .setOptions(options) .setTrackTransactionStarter(spanner.getOptions().isTrackTransactionStarter()) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java index b24393340a4..51a42525bfc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java @@ -37,6 +37,7 @@ import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS; import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS_WITH_TYPE; import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException; +import static com.google.common.base.Preconditions.checkState; import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; @@ -860,7 +861,7 @@ public TransactionContext resetForRetry() { } catch (SessionNotFoundException e) { session = sessionPool.replaceSession(e, session); PooledSession pooledSession = session.get(); - delegate = pooledSession.delegate.transactionManager(); + delegate = pooledSession.delegate.transactionManager(options); restartedAfterSessionNotFound = true; } } @@ -871,6 +872,11 @@ public Timestamp getCommitTimestamp() { return delegate.getCommitTimestamp(); } + @Override + public CommitResponse getCommitResponse() { + return delegate.getCommitResponse(); + } + @Override public void close() { if (closed) { @@ -949,6 +955,11 @@ public Timestamp getCommitTimestamp() { return getRunner().getCommitTimestamp(); } + @Override + public CommitResponse getCommitResponse() { + return getRunner().getCommitResponse(); + } + @Override public TransactionRunner allowNestedTransaction() { getRunner().allowNestedTransaction(); @@ -960,7 +971,7 @@ private static class SessionPoolAsyncRunner implements AsyncRunner { private final SessionPool sessionPool; private volatile PooledSessionFuture session; private final TransactionOption[] options; - private final SettableApiFuture commitTimestamp = SettableApiFuture.create(); + private SettableApiFuture commitResponse; private SessionPoolAsyncRunner( SessionPool sessionPool, PooledSessionFuture session, TransactionOption... options) { @@ -971,6 +982,7 @@ private SessionPoolAsyncRunner( @Override public ApiFuture runAsync(final AsyncWork work, Executor executor) { + commitResponse = SettableApiFuture.create(); final SettableApiFuture res = SettableApiFuture.create(); executor.execute( new Runnable() { @@ -1010,7 +1022,7 @@ public void run() { } session.get().markUsed(); session.close(); - setCommitTimestamp(runner); + setCommitResponse(runner); if (exception != null) { res.setException(exception); } else { @@ -1021,17 +1033,32 @@ public void run() { return res; } - private void setCommitTimestamp(AsyncRunner delegate) { + private void setCommitResponse(AsyncRunner delegate) { try { - commitTimestamp.set(delegate.getCommitTimestamp().get()); + commitResponse.set(delegate.getCommitResponse().get()); } catch (Throwable t) { - commitTimestamp.setException(t); + commitResponse.setException(t); } } @Override public ApiFuture getCommitTimestamp() { - return commitTimestamp; + checkState(commitResponse != null, "runAsync() has not yet been called"); + return ApiFutures.transform( + commitResponse, + new ApiFunction() { + @Override + public Timestamp apply(CommitResponse input) { + return input.getCommitTimestamp(); + } + }, + MoreExecutors.directExecutor()); + } + + @Override + public ApiFuture getCommitResponse() { + checkState(commitResponse != null, "runAsync() has not yet been called"); + return commitResponse; } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolAsyncTransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolAsyncTransactionManager.java index e618e2b44a3..83056603f28 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolAsyncTransactionManager.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolAsyncTransactionManager.java @@ -290,4 +290,22 @@ public TransactionState getState() { return txnState; } } + + public ApiFuture getCommitResponse() { + synchronized (lock) { + Preconditions.checkState( + txnState == TransactionState.COMMITTED, + "commit can only be invoked if the transaction was successfully committed"); + } + return ApiFutures.transformAsync( + delegate, + new ApiAsyncFunction() { + @Override + public ApiFuture apply(AsyncTransactionManagerImpl input) + throws Exception { + return input.getCommitResponse(); + } + }, + MoreExecutors.directExecutor()); + } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java index 2c68fd317e8..c7432ea40db 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java @@ -18,6 +18,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import java.util.Objects; import org.threeten.bp.Duration; /** Options for the session pool used by {@code DatabaseClient}. */ @@ -63,6 +64,48 @@ private SessionPoolOptions(Builder builder) { this.removeInactiveSessionAfter = builder.removeInactiveSessionAfter; } + @Override + public boolean equals(Object o) { + if (!(o instanceof SessionPoolOptions)) { + return false; + } + SessionPoolOptions other = (SessionPoolOptions) o; + return Objects.equals(this.minSessions, other.minSessions) + && Objects.equals(this.maxSessions, other.maxSessions) + && Objects.equals(this.incStep, other.incStep) + && Objects.equals(this.maxIdleSessions, other.maxIdleSessions) + && Objects.equals(this.writeSessionsFraction, other.writeSessionsFraction) + && Objects.equals(this.actionOnExhaustion, other.actionOnExhaustion) + && Objects.equals(this.actionOnSessionNotFound, other.actionOnSessionNotFound) + && Objects.equals(this.actionOnSessionLeak, other.actionOnSessionLeak) + && Objects.equals( + this.initialWaitForSessionTimeoutMillis, other.initialWaitForSessionTimeoutMillis) + && Objects.equals(this.loopFrequency, other.loopFrequency) + && Objects.equals(this.keepAliveIntervalMinutes, other.keepAliveIntervalMinutes) + && Objects.equals(this.removeInactiveSessionAfter, other.removeInactiveSessionAfter); + } + + @Override + public int hashCode() { + return Objects.hash( + this.minSessions, + this.maxSessions, + this.incStep, + this.maxIdleSessions, + this.writeSessionsFraction, + this.actionOnExhaustion, + this.actionOnSessionNotFound, + this.actionOnSessionLeak, + this.initialWaitForSessionTimeoutMillis, + this.loopFrequency, + this.keepAliveIntervalMinutes, + this.removeInactiveSessionAfter); + } + + public Builder toBuilder() { + return new Builder(this); + } + public int getMinSessions() { return minSessions; } @@ -165,6 +208,24 @@ public static class Builder { private int keepAliveIntervalMinutes = 30; private Duration removeInactiveSessionAfter = Duration.ofMinutes(55L); + public Builder() {} + + private Builder(SessionPoolOptions options) { + this.minSessionsSet = true; + this.minSessions = options.minSessions; + this.maxSessions = options.maxSessions; + this.incStep = options.incStep; + this.maxIdleSessions = options.maxIdleSessions; + this.writeSessionsFraction = options.writeSessionsFraction; + this.actionOnExhaustion = options.actionOnExhaustion; + this.initialWaitForSessionTimeoutMillis = options.initialWaitForSessionTimeoutMillis; + this.actionOnSessionNotFound = options.actionOnSessionNotFound; + this.actionOnSessionLeak = options.actionOnSessionLeak; + this.loopFrequency = options.loopFrequency; + this.keepAliveIntervalMinutes = options.keepAliveIntervalMinutes; + this.removeInactiveSessionAfter = options.removeInactiveSessionAfter; + } + /** * Minimum number of sessions that this pool will always maintain. These will be created eagerly * in parallel. Defaults to 100. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java index 2d034eda883..9fa8b30be28 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java @@ -25,6 +25,7 @@ import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.spanner.SessionClient.SessionId; import com.google.cloud.spanner.SpannerOptions.CloseableExecutorProvider; +import com.google.cloud.spanner.spi.v1.GapicSpannerRpc; import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated; import com.google.common.annotations.VisibleForTesting; @@ -269,7 +270,11 @@ void close(long timeout, TimeUnit unit) { sessionClients.clear(); asyncExecutorProvider.close(); try { - gapicRpc.shutdown(); + if (timeout == Long.MAX_VALUE || !(gapicRpc instanceof GapicSpannerRpc)) { + gapicRpc.shutdown(); + } else { + ((GapicSpannerRpc) gapicRpc).shutdownNow(); + } } catch (RuntimeException e) { logger.log(Level.WARNING, "Failed to close channels", e); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManager.java index 34e21657152..c9a56d8100a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManager.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManager.java @@ -87,6 +87,9 @@ public enum TransactionState { */ Timestamp getCommitTimestamp(); + /** Returns the {@link CommitResponse} of this transaction. */ + CommitResponse getCommitResponse(); + /** Returns the state of the transaction. */ TransactionState getState(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java index 1cef304e483..2d47fcd5c78 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java @@ -118,7 +118,15 @@ public Timestamp getCommitTimestamp() { Preconditions.checkState( txnState == TransactionState.COMMITTED, "getCommitTimestamp can only be invoked if the transaction committed successfully"); - return txn.commitTimestamp(); + return txn.getCommitResponse().getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + Preconditions.checkState( + txnState == TransactionState.COMMITTED, + "getCommitResponse can only be invoked if the transaction committed successfully"); + return txn.getCommitResponse(); } @Override diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunner.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunner.java index 2ad27fcd7b5..33cb57e90fe 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunner.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunner.java @@ -74,6 +74,9 @@ interface TransactionCallable { */ Timestamp getCommitTimestamp(); + /** Returns the {@link CommitResponse} of this transaction. */ + CommitResponse getCommitResponse(); + /** * Allows overriding the default behaviour of blocking nested transactions. * diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java index c3973c1807d..dbbbca068a0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java @@ -31,7 +31,6 @@ import com.google.cloud.spanner.Options.TransactionOption; import com.google.cloud.spanner.Options.UpdateOption; import com.google.cloud.spanner.SessionImpl.SessionTransaction; -import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; @@ -40,7 +39,6 @@ import com.google.protobuf.Empty; import com.google.rpc.Code; import com.google.spanner.v1.CommitRequest; -import com.google.spanner.v1.CommitResponse; import com.google.spanner.v1.ExecuteBatchDmlRequest; import com.google.spanner.v1.ExecuteBatchDmlResponse; import com.google.spanner.v1.ExecuteSqlRequest; @@ -184,7 +182,7 @@ public void removeListener(Runnable listener) { volatile ByteString transactionId; - private Timestamp commitTimestamp; + private CommitResponse commitResponse; private TransactionContextImpl(Builder builder) { super(builder); @@ -275,7 +273,7 @@ public void run() { void commit() { try { - commitTimestamp = commitAsync().get(); + commitResponse = commitAsync().get(); } catch (InterruptedException e) { if (commitFuture != null) { commitFuture.cancel(true); @@ -288,10 +286,13 @@ void commit() { volatile ApiFuture commitFuture; - ApiFuture commitAsync() { - final SettableApiFuture res = SettableApiFuture.create(); + ApiFuture commitAsync() { + final SettableApiFuture res = SettableApiFuture.create(); final SettableApiFuture finishOps; - CommitRequest.Builder builder = CommitRequest.newBuilder().setSession(session.getName()); + CommitRequest.Builder builder = + CommitRequest.newBuilder() + .setSession(session.getName()) + .setReturnCommitStats(options.withCommitStats()); synchronized (lock) { if (transactionIdFuture == null && transactionId == null && runningAsyncOperations == 0) { finishOps = SettableApiFuture.create(); @@ -313,12 +314,12 @@ ApiFuture commitAsync() { } private final class CommitRunnable implements Runnable { - private final SettableApiFuture res; + private final SettableApiFuture res; private final ApiFuture prev; private final CommitRequest.Builder requestBuilder; CommitRunnable( - SettableApiFuture res, + SettableApiFuture res, ApiFuture prev, CommitRequest.Builder requestBuilder) { this.res = res; @@ -342,7 +343,7 @@ public void run() { span.addAnnotation("Starting Commit"); final Span opSpan = tracer.spanBuilderWithExplicitParent(SpannerImpl.COMMIT, span).startSpan(); - final ApiFuture commitFuture = + final ApiFuture commitFuture = rpc.commitAsync(commitRequest, session.getOptions()); commitFuture.addListener( tracer.withSpan( @@ -351,15 +352,14 @@ public void run() { @Override public void run() { try { - CommitResponse commitResponse = commitFuture.get(); - if (!commitResponse.hasCommitTimestamp()) { + com.google.spanner.v1.CommitResponse proto = commitFuture.get(); + if (!proto.hasCommitTimestamp()) { throw newSpannerException( ErrorCode.INTERNAL, "Missing commitTimestamp:\n" + session.getName()); } - Timestamp ts = Timestamp.fromProto(commitResponse.getCommitTimestamp()); span.addAnnotation("Commit Done"); opSpan.end(TraceUtil.END_SPAN_OPTIONS); - res.set(ts); + res.set(new CommitResponse(proto)); } catch (Throwable e) { if (e instanceof ExecutionException) { e = @@ -387,9 +387,9 @@ public void run() { } } - Timestamp commitTimestamp() { - checkState(commitTimestamp != null, "run() has not yet returned normally"); - return commitTimestamp; + CommitResponse getCommitResponse() { + checkState(commitResponse != null, "run() has not yet returned normally"); + return commitResponse; } boolean isAborted() { @@ -463,7 +463,10 @@ TransactionSelector getTransactionSelector() { // Aborted error if the call that included the BeginTransaction option fails. The // Aborted error will cause the entire transaction to be retried, and the retry will use // a separate BeginTransaction RPC. - TransactionSelector.newBuilder() + // If tx.get() returns successfully, this.transactionId will also have been set to a + // valid value as the latter is always set when a transaction id is returned by a + // statement. + return TransactionSelector.newBuilder() .setId(tx.get(waitForTransactionTimeoutMillis, TimeUnit.MILLISECONDS)) .build(); } @@ -823,11 +826,7 @@ public TransactionRunner allowNestedTransaction() { return this; } - TransactionRunnerImpl( - SessionImpl session, - SpannerRpc rpc, - int defaultPrefetchChunks, - TransactionOption... options) { + TransactionRunnerImpl(SessionImpl session, TransactionOption... options) { this.session = session; this.options = Options.fromTransactionOptions(options); this.txn = session.newTransaction(this.options); @@ -952,7 +951,12 @@ public T call() { @Override public Timestamp getCommitTimestamp() { checkState(txn != null, "run() has not yet returned normally"); - return txn.commitTimestamp(); + return txn.getCommitResponse().getCommitTimestamp(); + } + + public CommitResponse getCommitResponse() { + checkState(txn != null, "run() has not yet returned normally"); + return txn.getCommitResponse(); } @Override diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClient.java index c30e4ab1269..9e829e30d4e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClient.java @@ -85,6 +85,13 @@ *

This class provides the ability to make remote calls to the backing service through method * calls that map to API methods. Sample code to get started: * + *

{@code
+ * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+ *   DatabaseName name = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+ *   Database response = databaseAdminClient.getDatabase(name);
+ * }
+ * }
+ * *

Note: close() needs to be called on the DatabaseAdminClient object to clean up resources such * as threads. In the example above, try-with-resources is used, which automatically calls close(). * @@ -199,6 +206,17 @@ public final OperationsClient getOperationsClient() { /** * Lists Cloud Spanner databases. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   for (Database element : databaseAdminClient.listDatabases(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance whose databases should be listed. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -215,6 +233,17 @@ public final ListDatabasesPagedResponse listDatabases(InstanceName parent) { /** * Lists Cloud Spanner databases. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   for (Database element : databaseAdminClient.listDatabases(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance whose databases should be listed. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -228,6 +257,22 @@ public final ListDatabasesPagedResponse listDatabases(String parent) { /** * Lists Cloud Spanner databases. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListDatabasesRequest request =
+   *       ListDatabasesRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   for (Database element : databaseAdminClient.listDatabases(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -240,6 +285,23 @@ public final ListDatabasesPagedResponse listDatabases(ListDatabasesRequest reque * Lists Cloud Spanner databases. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListDatabasesRequest request =
+   *       ListDatabasesRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.listDatabasesPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (Database element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listDatabasesPagedCallable() { @@ -251,6 +313,23 @@ public final ListDatabasesPagedResponse listDatabases(ListDatabasesRequest reque * Lists Cloud Spanner databases. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   while (true) {
+   *     ListDatabasesResponse response = databaseAdminClient.listDatabasesCallable().call(request);
+   *     for (Database element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listDatabasesCallable() { return stub.listDatabasesCallable(); @@ -266,6 +345,16 @@ public final UnaryCallable listData * [response][google.longrunning.Operation.response] field type is * [Database][google.spanner.admin.database.v1.Database], if successful. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   String createStatement = "createStatement744686547";
+   *   Database response = databaseAdminClient.createDatabaseAsync(parent, createStatement).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance that will serve the new database. Values are * of the form `projects/<project>/instances/<instance>`. * @param createStatement Required. A `CREATE DATABASE` statement, which specifies the ID of the @@ -295,6 +384,16 @@ public final OperationFuture createDatabaseAsy * [response][google.longrunning.Operation.response] field type is * [Database][google.spanner.admin.database.v1.Database], if successful. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   String createStatement = "createStatement744686547";
+   *   Database response = databaseAdminClient.createDatabaseAsync(parent, createStatement).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance that will serve the new database. Values are * of the form `projects/<project>/instances/<instance>`. * @param createStatement Required. A `CREATE DATABASE` statement, which specifies the ID of the @@ -324,6 +423,20 @@ public final OperationFuture createDatabaseAsy * [response][google.longrunning.Operation.response] field type is * [Database][google.spanner.admin.database.v1.Database], if successful. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateDatabaseRequest request =
+   *       CreateDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setCreateStatement("createStatement744686547")
+   *           .addAllExtraStatements(new ArrayList())
+   *           .build();
+   *   Database response = databaseAdminClient.createDatabaseAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -343,6 +456,21 @@ public final OperationFuture createDatabaseAsy * [Database][google.spanner.admin.database.v1.Database], if successful. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateDatabaseRequest request =
+   *       CreateDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setCreateStatement("createStatement744686547")
+   *           .addAllExtraStatements(new ArrayList())
+   *           .build();
+   *   OperationFuture future =
+   *       databaseAdminClient.createDatabaseOperationCallable().futureCall(request);
+   *   // Do something.
+   *   Database response = future.get();
+   * }
+   * }
*/ public final OperationCallable createDatabaseOperationCallable() { @@ -360,6 +488,21 @@ public final OperationFuture createDatabaseAsy * [Database][google.spanner.admin.database.v1.Database], if successful. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateDatabaseRequest request =
+   *       CreateDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setCreateStatement("createStatement744686547")
+   *           .addAllExtraStatements(new ArrayList())
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.createDatabaseCallable().futureCall(request);
+   *   // Do something.
+   *   Operation response = future.get();
+   * }
+   * }
*/ public final UnaryCallable createDatabaseCallable() { return stub.createDatabaseCallable(); @@ -369,6 +512,15 @@ public final UnaryCallable createDatabaseCalla /** * Gets the state of a Cloud Spanner database. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DatabaseName name = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   Database response = databaseAdminClient.getDatabase(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested database. Values are of the form * `projects/<project>/instances/<instance>/databases/<database>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -383,6 +535,15 @@ public final Database getDatabase(DatabaseName name) { /** * Gets the state of a Cloud Spanner database. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String name = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   Database response = databaseAdminClient.getDatabase(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested database. Values are of the form * `projects/<project>/instances/<instance>/databases/<database>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -396,6 +557,18 @@ public final Database getDatabase(String name) { /** * Gets the state of a Cloud Spanner database. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetDatabaseRequest request =
+   *       GetDatabaseRequest.newBuilder()
+   *           .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   Database response = databaseAdminClient.getDatabase(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -408,6 +581,18 @@ public final Database getDatabase(GetDatabaseRequest request) { * Gets the state of a Cloud Spanner database. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetDatabaseRequest request =
+   *       GetDatabaseRequest.newBuilder()
+   *           .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.getDatabaseCallable().futureCall(request);
+   *   // Do something.
+   *   Database response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getDatabaseCallable() { return stub.getDatabaseCallable(); @@ -423,6 +608,16 @@ public final UnaryCallable getDatabaseCallable() { * [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. The * operation has no response. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   List statements = new ArrayList<>();
+   *   databaseAdminClient.updateDatabaseDdlAsync(database, statements).get();
+   * }
+   * }
+ * * @param database Required. The database to update. * @param statements Required. DDL statements to be applied to the database. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -447,6 +642,16 @@ public final OperationFuture updateDatabaseDdl * [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. The * operation has no response. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   List statements = new ArrayList<>();
+   *   databaseAdminClient.updateDatabaseDdlAsync(database, statements).get();
+   * }
+   * }
+ * * @param database Required. The database to update. * @param statements Required. DDL statements to be applied to the database. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -471,6 +676,20 @@ public final OperationFuture updateDatabaseDdl * [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. The * operation has no response. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   UpdateDatabaseDdlRequest request =
+   *       UpdateDatabaseDdlRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .addAllStatements(new ArrayList())
+   *           .setOperationId("operationId129704162")
+   *           .build();
+   *   databaseAdminClient.updateDatabaseDdlAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -490,6 +709,21 @@ public final OperationFuture updateDatabaseDdl * operation has no response. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   UpdateDatabaseDdlRequest request =
+   *       UpdateDatabaseDdlRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .addAllStatements(new ArrayList())
+   *           .setOperationId("operationId129704162")
+   *           .build();
+   *   OperationFuture future =
+   *       databaseAdminClient.updateDatabaseDdlOperationCallable().futureCall(request);
+   *   // Do something.
+   *   future.get();
+   * }
+   * }
*/ public final OperationCallable updateDatabaseDdlOperationCallable() { @@ -507,6 +741,21 @@ public final OperationFuture updateDatabaseDdl * operation has no response. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   UpdateDatabaseDdlRequest request =
+   *       UpdateDatabaseDdlRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .addAllStatements(new ArrayList())
+   *           .setOperationId("operationId129704162")
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.updateDatabaseDdlCallable().futureCall(request);
+   *   // Do something.
+   *   future.get();
+   * }
+   * }
*/ public final UnaryCallable updateDatabaseDdlCallable() { return stub.updateDatabaseDdlCallable(); @@ -517,6 +766,15 @@ public final UnaryCallable updateDatabaseDd * Drops (aka deletes) a Cloud Spanner database. Completed backups for the database will be * retained according to their `expire_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   databaseAdminClient.dropDatabase(database);
+   * }
+   * }
+ * * @param database Required. The database to be dropped. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -533,6 +791,15 @@ public final void dropDatabase(DatabaseName database) { * Drops (aka deletes) a Cloud Spanner database. Completed backups for the database will be * retained according to their `expire_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   databaseAdminClient.dropDatabase(database);
+   * }
+   * }
+ * * @param database Required. The database to be dropped. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -546,6 +813,18 @@ public final void dropDatabase(String database) { * Drops (aka deletes) a Cloud Spanner database. Completed backups for the database will be * retained according to their `expire_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DropDatabaseRequest request =
+   *       DropDatabaseRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   databaseAdminClient.dropDatabase(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -559,6 +838,18 @@ public final void dropDatabase(DropDatabaseRequest request) { * retained according to their `expire_time`. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DropDatabaseRequest request =
+   *       DropDatabaseRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.dropDatabaseCallable().futureCall(request);
+   *   // Do something.
+   *   future.get();
+   * }
+   * }
*/ public final UnaryCallable dropDatabaseCallable() { return stub.dropDatabaseCallable(); @@ -570,7 +861,17 @@ public final UnaryCallable dropDatabaseCallable() { * method does not show pending schema updates, those may be queried using the * [Operations][google.longrunning.Operations] API. * - * @param database Required. The database whose schema we wish to get. + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   GetDatabaseDdlResponse response = databaseAdminClient.getDatabaseDdl(database);
+   * }
+   * }
+ * + * @param database Required. The database whose schema we wish to get. Values are of the form + * `projects/<project>/instances/<instance>/databases/<database>` * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ public final GetDatabaseDdlResponse getDatabaseDdl(DatabaseName database) { @@ -587,7 +888,17 @@ public final GetDatabaseDdlResponse getDatabaseDdl(DatabaseName database) { * method does not show pending schema updates, those may be queried using the * [Operations][google.longrunning.Operations] API. * - * @param database Required. The database whose schema we wish to get. + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   GetDatabaseDdlResponse response = databaseAdminClient.getDatabaseDdl(database);
+   * }
+   * }
+ * + * @param database Required. The database whose schema we wish to get. Values are of the form + * `projects/<project>/instances/<instance>/databases/<database>` * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ public final GetDatabaseDdlResponse getDatabaseDdl(String database) { @@ -602,6 +913,18 @@ public final GetDatabaseDdlResponse getDatabaseDdl(String database) { * method does not show pending schema updates, those may be queried using the * [Operations][google.longrunning.Operations] API. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetDatabaseDdlRequest request =
+   *       GetDatabaseDdlRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   GetDatabaseDdlResponse response = databaseAdminClient.getDatabaseDdl(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -616,6 +939,19 @@ public final GetDatabaseDdlResponse getDatabaseDdl(GetDatabaseDdlRequest request * [Operations][google.longrunning.Operations] API. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetDatabaseDdlRequest request =
+   *       GetDatabaseDdlRequest.newBuilder()
+   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.getDatabaseDdlCallable().futureCall(request);
+   *   // Do something.
+   *   GetDatabaseDdlResponse response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getDatabaseDdlCallable() { @@ -631,6 +967,16 @@ public final GetDatabaseDdlResponse getDatabaseDdl(GetDatabaseDdlRequest request * `spanner.backups.setIamPolicy` permission on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ResourceName resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   Policy policy = Policy.newBuilder().build();
+   *   Policy response = databaseAdminClient.setIamPolicy(resource, policy);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being specified. See the * operation documentation for the appropriate value for this field. * @param policy REQUIRED: The complete policy to be applied to the `resource`. The size of the @@ -656,6 +1002,16 @@ public final Policy setIamPolicy(ResourceName resource, Policy policy) { * `spanner.backups.setIamPolicy` permission on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   Policy policy = Policy.newBuilder().build();
+   *   Policy response = databaseAdminClient.setIamPolicy(resource, policy);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being specified. See the * operation documentation for the appropriate value for this field. * @param policy REQUIRED: The complete policy to be applied to the `resource`. The size of the @@ -678,6 +1034,19 @@ public final Policy setIamPolicy(String resource, Policy policy) { * `spanner.backups.setIamPolicy` permission on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   SetIamPolicyRequest request =
+   *       SetIamPolicyRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .setPolicy(Policy.newBuilder().build())
+   *           .build();
+   *   Policy response = databaseAdminClient.setIamPolicy(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -695,6 +1064,19 @@ public final Policy setIamPolicy(SetIamPolicyRequest request) { * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   SetIamPolicyRequest request =
+   *       SetIamPolicyRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .setPolicy(Policy.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.setIamPolicyCallable().futureCall(request);
+   *   // Do something.
+   *   Policy response = future.get();
+   * }
+   * }
*/ public final UnaryCallable setIamPolicyCallable() { return stub.setIamPolicyCallable(); @@ -710,6 +1092,15 @@ public final UnaryCallable setIamPolicyCallable() { * `spanner.backups.getIamPolicy` permission on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ResourceName resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   Policy response = databaseAdminClient.getIamPolicy(resource);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being requested. See the * operation documentation for the appropriate value for this field. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -732,6 +1123,15 @@ public final Policy getIamPolicy(ResourceName resource) { * `spanner.backups.getIamPolicy` permission on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   Policy response = databaseAdminClient.getIamPolicy(resource);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being requested. See the * operation documentation for the appropriate value for this field. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -751,6 +1151,19 @@ public final Policy getIamPolicy(String resource) { * `spanner.backups.getIamPolicy` permission on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetIamPolicyRequest request =
+   *       GetIamPolicyRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .setOptions(GetPolicyOptions.newBuilder().build())
+   *           .build();
+   *   Policy response = databaseAdminClient.getIamPolicy(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -769,6 +1182,19 @@ public final Policy getIamPolicy(GetIamPolicyRequest request) { * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetIamPolicyRequest request =
+   *       GetIamPolicyRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .setOptions(GetPolicyOptions.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.getIamPolicyCallable().futureCall(request);
+   *   // Do something.
+   *   Policy response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getIamPolicyCallable() { return stub.getIamPolicyCallable(); @@ -784,6 +1210,17 @@ public final UnaryCallable getIamPolicyCallable() { * does not exist will result in a NOT_FOUND error if the user has `spanner.backups.list` * permission on the containing instance. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ResourceName resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+   *   List permissions = new ArrayList<>();
+   *   TestIamPermissionsResponse response =
+   *       databaseAdminClient.testIamPermissions(resource, permissions);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy detail is being requested. See the * operation documentation for the appropriate value for this field. * @param permissions The set of permissions to check for the `resource`. Permissions with @@ -811,6 +1248,17 @@ public final TestIamPermissionsResponse testIamPermissions( * does not exist will result in a NOT_FOUND error if the user has `spanner.backups.list` * permission on the containing instance. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String resource = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
+   *   List permissions = new ArrayList<>();
+   *   TestIamPermissionsResponse response =
+   *       databaseAdminClient.testIamPermissions(resource, permissions);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy detail is being requested. See the * operation documentation for the appropriate value for this field. * @param permissions The set of permissions to check for the `resource`. Permissions with @@ -838,6 +1286,19 @@ public final TestIamPermissionsResponse testIamPermissions( * does not exist will result in a NOT_FOUND error if the user has `spanner.backups.list` * permission on the containing instance. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   TestIamPermissionsRequest request =
+   *       TestIamPermissionsRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .addAllPermissions(new ArrayList())
+   *           .build();
+   *   TestIamPermissionsResponse response = databaseAdminClient.testIamPermissions(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -856,6 +1317,20 @@ public final TestIamPermissionsResponse testIamPermissions(TestIamPermissionsReq * permission on the containing instance. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   TestIamPermissionsRequest request =
+   *       TestIamPermissionsRequest.newBuilder()
+   *           .setResource(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
+   *           .addAllPermissions(new ArrayList())
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.testIamPermissionsCallable().futureCall(request);
+   *   // Do something.
+   *   TestIamPermissionsResponse response = future.get();
+   * }
+   * }
*/ public final UnaryCallable testIamPermissionsCallable() { @@ -875,6 +1350,17 @@ public final TestIamPermissionsResponse testIamPermissions(TestIamPermissionsReq * operation will stop the creation and delete the backup. There can be only one pending backup * creation per database. Backup creation of different databases can run concurrently. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   Backup backup = Backup.newBuilder().build();
+   *   String backupId = "backupId2121930365";
+   *   Backup response = databaseAdminClient.createBackupAsync(parent, backup, backupId).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which the backup will be created. This must * be the same instance that contains the database the backup will be created from. The backup * will be stored in the location(s) specified in the instance configuration of this instance. @@ -909,6 +1395,17 @@ public final OperationFuture createBackupAsync( * operation will stop the creation and delete the backup. There can be only one pending backup * creation per database. Backup creation of different databases can run concurrently. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   Backup backup = Backup.newBuilder().build();
+   *   String backupId = "backupId2121930365";
+   *   Backup response = databaseAdminClient.createBackupAsync(parent, backup, backupId).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which the backup will be created. This must * be the same instance that contains the database the backup will be created from. The backup * will be stored in the location(s) specified in the instance configuration of this instance. @@ -943,6 +1440,20 @@ public final OperationFuture createBackupAsync( * operation will stop the creation and delete the backup. There can be only one pending backup * creation per database. Backup creation of different databases can run concurrently. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateBackupRequest request =
+   *       CreateBackupRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setBackupId("backupId2121930365")
+   *           .setBackup(Backup.newBuilder().build())
+   *           .build();
+   *   Backup response = databaseAdminClient.createBackupAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -965,6 +1476,21 @@ public final OperationFuture createBackupAsync( * creation per database. Backup creation of different databases can run concurrently. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateBackupRequest request =
+   *       CreateBackupRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setBackupId("backupId2121930365")
+   *           .setBackup(Backup.newBuilder().build())
+   *           .build();
+   *   OperationFuture future =
+   *       databaseAdminClient.createBackupOperationCallable().futureCall(request);
+   *   // Do something.
+   *   Backup response = future.get();
+   * }
+   * }
*/ public final OperationCallable createBackupOperationCallable() { @@ -985,6 +1511,20 @@ public final OperationFuture createBackupAsync( * creation per database. Backup creation of different databases can run concurrently. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   CreateBackupRequest request =
+   *       CreateBackupRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setBackupId("backupId2121930365")
+   *           .setBackup(Backup.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.createBackupCallable().futureCall(request);
+   *   // Do something.
+   *   Operation response = future.get();
+   * }
+   * }
*/ public final UnaryCallable createBackupCallable() { return stub.createBackupCallable(); @@ -994,6 +1534,15 @@ public final UnaryCallable createBackupCallable( /** * Gets metadata on a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   BackupName name = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]");
+   *   Backup response = databaseAdminClient.getBackup(name);
+   * }
+   * }
+ * * @param name Required. Name of the backup. Values are of the form * `projects/<project>/instances/<instance>/backups/<backup>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1008,6 +1557,15 @@ public final Backup getBackup(BackupName name) { /** * Gets metadata on a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String name = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString();
+   *   Backup response = databaseAdminClient.getBackup(name);
+   * }
+   * }
+ * * @param name Required. Name of the backup. Values are of the form * `projects/<project>/instances/<instance>/backups/<backup>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1021,6 +1579,18 @@ public final Backup getBackup(String name) { /** * Gets metadata on a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetBackupRequest request =
+   *       GetBackupRequest.newBuilder()
+   *           .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString())
+   *           .build();
+   *   Backup response = databaseAdminClient.getBackup(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1033,6 +1603,18 @@ public final Backup getBackup(GetBackupRequest request) { * Gets metadata on a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   GetBackupRequest request =
+   *       GetBackupRequest.newBuilder()
+   *           .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.getBackupCallable().futureCall(request);
+   *   // Do something.
+   *   Backup response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getBackupCallable() { return stub.getBackupCallable(); @@ -1042,6 +1624,16 @@ public final UnaryCallable getBackupCallable() { /** * Updates a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   Backup backup = Backup.newBuilder().build();
+   *   FieldMask updateMask = FieldMask.newBuilder().build();
+   *   Backup response = databaseAdminClient.updateBackup(backup, updateMask);
+   * }
+   * }
+ * * @param backup Required. The backup to update. `backup.name`, and the fields to be updated as * specified by `update_mask` are required. Other fields are ignored. Update is only supported * for the following fields: * `backup.expire_time`. @@ -1061,6 +1653,19 @@ public final Backup updateBackup(Backup backup, FieldMask updateMask) { /** * Updates a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   UpdateBackupRequest request =
+   *       UpdateBackupRequest.newBuilder()
+   *           .setBackup(Backup.newBuilder().build())
+   *           .setUpdateMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   Backup response = databaseAdminClient.updateBackup(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1073,6 +1678,19 @@ public final Backup updateBackup(UpdateBackupRequest request) { * Updates a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   UpdateBackupRequest request =
+   *       UpdateBackupRequest.newBuilder()
+   *           .setBackup(Backup.newBuilder().build())
+   *           .setUpdateMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.updateBackupCallable().futureCall(request);
+   *   // Do something.
+   *   Backup response = future.get();
+   * }
+   * }
*/ public final UnaryCallable updateBackupCallable() { return stub.updateBackupCallable(); @@ -1082,6 +1700,15 @@ public final UnaryCallable updateBackupCallable() { /** * Deletes a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   BackupName name = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]");
+   *   databaseAdminClient.deleteBackup(name);
+   * }
+   * }
+ * * @param name Required. Name of the backup to delete. Values are of the form * `projects/<project>/instances/<instance>/backups/<backup>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1096,6 +1723,15 @@ public final void deleteBackup(BackupName name) { /** * Deletes a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String name = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString();
+   *   databaseAdminClient.deleteBackup(name);
+   * }
+   * }
+ * * @param name Required. Name of the backup to delete. Values are of the form * `projects/<project>/instances/<instance>/backups/<backup>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1109,6 +1745,18 @@ public final void deleteBackup(String name) { /** * Deletes a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DeleteBackupRequest request =
+   *       DeleteBackupRequest.newBuilder()
+   *           .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString())
+   *           .build();
+   *   databaseAdminClient.deleteBackup(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1121,6 +1769,18 @@ public final void deleteBackup(DeleteBackupRequest request) { * Deletes a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   DeleteBackupRequest request =
+   *       DeleteBackupRequest.newBuilder()
+   *           .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString())
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.deleteBackupCallable().futureCall(request);
+   *   // Do something.
+   *   future.get();
+   * }
+   * }
*/ public final UnaryCallable deleteBackupCallable() { return stub.deleteBackupCallable(); @@ -1131,6 +1791,17 @@ public final UnaryCallable deleteBackupCallable() { * Lists completed and pending backups. Backups returned are ordered by `create_time` in * descending order, starting from the most recent `create_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   for (Backup element : databaseAdminClient.listBackups(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance to list backups from. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1148,6 +1819,17 @@ public final ListBackupsPagedResponse listBackups(InstanceName parent) { * Lists completed and pending backups. Backups returned are ordered by `create_time` in * descending order, starting from the most recent `create_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   for (Backup element : databaseAdminClient.listBackups(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance to list backups from. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1162,6 +1844,23 @@ public final ListBackupsPagedResponse listBackups(String parent) { * Lists completed and pending backups. Backups returned are ordered by `create_time` in * descending order, starting from the most recent `create_time`. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListBackupsRequest request =
+   *       ListBackupsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   for (Backup element : databaseAdminClient.listBackups(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1175,6 +1874,23 @@ public final ListBackupsPagedResponse listBackups(ListBackupsRequest request) { * descending order, starting from the most recent `create_time`. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListBackupsRequest request =
+   *       ListBackupsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   ApiFuture future = databaseAdminClient.listBackupsPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (Backup element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listBackupsPagedCallable() { @@ -1187,6 +1903,23 @@ public final ListBackupsPagedResponse listBackups(ListBackupsRequest request) { * descending order, starting from the most recent `create_time`. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   while (true) {
+   *     ListBackupsResponse response = databaseAdminClient.listBackupsCallable().call(request);
+   *     for (Backup element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listBackupsCallable() { return stub.listBackupsCallable(); @@ -1209,6 +1942,18 @@ public final UnaryCallable listBackupsC * operation can be initiated, without waiting for the optimize operation associated with the * first restore to complete. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   String databaseId = "databaseId1688905718";
+   *   BackupName backup = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]");
+   *   Database response =
+   *       databaseAdminClient.restoreDatabaseAsync(parent, databaseId, backup).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which to create the restored database. This * instance must be in the same project and have the same instance configuration as the * instance containing the source backup. Values are of the form @@ -1249,6 +1994,18 @@ public final OperationFuture restoreDatabaseA * operation can be initiated, without waiting for the optimize operation associated with the * first restore to complete. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   String databaseId = "databaseId1688905718";
+   *   String backup = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString();
+   *   Database response =
+   *       databaseAdminClient.restoreDatabaseAsync(parent, databaseId, backup).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which to create the restored database. This * instance must be in the same project and have the same instance configuration as the * instance containing the source backup. Values are of the form @@ -1289,6 +2046,18 @@ public final OperationFuture restoreDatabaseA * operation can be initiated, without waiting for the optimize operation associated with the * first restore to complete. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   String databaseId = "databaseId1688905718";
+   *   BackupName backup = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]");
+   *   Database response =
+   *       databaseAdminClient.restoreDatabaseAsync(parent, databaseId, backup).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which to create the restored database. This * instance must be in the same project and have the same instance configuration as the * instance containing the source backup. Values are of the form @@ -1329,6 +2098,18 @@ public final OperationFuture restoreDatabaseA * operation can be initiated, without waiting for the optimize operation associated with the * first restore to complete. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   String databaseId = "databaseId1688905718";
+   *   String backup = BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString();
+   *   Database response =
+   *       databaseAdminClient.restoreDatabaseAsync(parent, databaseId, backup).get();
+   * }
+   * }
+ * * @param parent Required. The name of the instance in which to create the restored database. This * instance must be in the same project and have the same instance configuration as the * instance containing the source backup. Values are of the form @@ -1369,6 +2150,19 @@ public final OperationFuture restoreDatabaseA * operation can be initiated, without waiting for the optimize operation associated with the * first restore to complete. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   RestoreDatabaseRequest request =
+   *       RestoreDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setDatabaseId("databaseId1688905718")
+   *           .build();
+   *   Database response = databaseAdminClient.restoreDatabaseAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1395,6 +2189,20 @@ public final OperationFuture restoreDatabaseA * first restore to complete. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   RestoreDatabaseRequest request =
+   *       RestoreDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setDatabaseId("databaseId1688905718")
+   *           .build();
+   *   OperationFuture future =
+   *       databaseAdminClient.restoreDatabaseOperationCallable().futureCall(request);
+   *   // Do something.
+   *   Database response = future.get();
+   * }
+   * }
*/ public final OperationCallable restoreDatabaseOperationCallable() { @@ -1419,6 +2227,20 @@ public final OperationFuture restoreDatabaseA * first restore to complete. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   RestoreDatabaseRequest request =
+   *       RestoreDatabaseRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setDatabaseId("databaseId1688905718")
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.restoreDatabaseCallable().futureCall(request);
+   *   // Do something.
+   *   Operation response = future.get();
+   * }
+   * }
*/ public final UnaryCallable restoreDatabaseCallable() { return stub.restoreDatabaseCallable(); @@ -1433,6 +2255,17 @@ public final UnaryCallable restoreDatabaseCal * `metadata.type_url` describes the type of the metadata. Operations returned include those that * have completed/failed/canceled within the last 7 days, and pending operations. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   for (Operation element : databaseAdminClient.listDatabaseOperations(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance of the database operations. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1454,6 +2287,17 @@ public final ListDatabaseOperationsPagedResponse listDatabaseOperations(Instance * `metadata.type_url` describes the type of the metadata. Operations returned include those that * have completed/failed/canceled within the last 7 days, and pending operations. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   for (Operation element : databaseAdminClient.listDatabaseOperations(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance of the database operations. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1473,6 +2317,23 @@ public final ListDatabaseOperationsPagedResponse listDatabaseOperations(String p * `metadata.type_url` describes the type of the metadata. Operations returned include those that * have completed/failed/canceled within the last 7 days, and pending operations. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListDatabaseOperationsRequest request =
+   *       ListDatabaseOperationsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   for (Operation element : databaseAdminClient.listDatabaseOperations(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1491,6 +2352,24 @@ public final ListDatabaseOperationsPagedResponse listDatabaseOperations( * have completed/failed/canceled within the last 7 days, and pending operations. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListDatabaseOperationsRequest request =
+   *       ListDatabaseOperationsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.listDatabaseOperationsPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (Operation element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listDatabaseOperationsPagedCallable() { @@ -1507,6 +2386,24 @@ public final ListDatabaseOperationsPagedResponse listDatabaseOperations( * have completed/failed/canceled within the last 7 days, and pending operations. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   while (true) {
+   *     ListDatabaseOperationsResponse response =
+   *         databaseAdminClient.listDatabaseOperationsCallable().call(request);
+   *     for (Operation element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listDatabaseOperationsCallable() { @@ -1524,6 +2421,17 @@ public final ListDatabaseOperationsPagedResponse listDatabaseOperations( * returned are ordered by `operation.metadata.value.progress.start_time` in descending order * starting from the most recently started operation. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   InstanceName parent = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   for (Operation element : databaseAdminClient.listBackupOperations(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance of the backup operations. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1547,6 +2455,17 @@ public final ListBackupOperationsPagedResponse listBackupOperations(InstanceName * returned are ordered by `operation.metadata.value.progress.start_time` in descending order * starting from the most recently started operation. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   String parent = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   for (Operation element : databaseAdminClient.listBackupOperations(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The instance of the backup operations. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1568,6 +2487,23 @@ public final ListBackupOperationsPagedResponse listBackupOperations(String paren * returned are ordered by `operation.metadata.value.progress.start_time` in descending order * starting from the most recently started operation. * + *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListBackupOperationsRequest request =
+   *       ListBackupOperationsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   for (Operation element : databaseAdminClient.listBackupOperations(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1588,6 +2524,24 @@ public final ListBackupOperationsPagedResponse listBackupOperations( * starting from the most recently started operation. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   ListBackupOperationsRequest request =
+   *       ListBackupOperationsRequest.newBuilder()
+   *           .setParent(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFilter("filter-1274492040")
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   ApiFuture future =
+   *       databaseAdminClient.listBackupOperationsPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (Operation element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listBackupOperationsPagedCallable() { @@ -1606,6 +2560,24 @@ public final ListBackupOperationsPagedResponse listBackupOperations( * starting from the most recently started operation. * *

Sample code: + * + *

{@code
+   * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+   *   while (true) {
+   *     ListBackupOperationsResponse response =
+   *         databaseAdminClient.listBackupOperationsCallable().call(request);
+   *     for (Operation element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listBackupOperationsCallable() { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/package-info.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/package-info.java index a7a4a8722e0..6723f47e828 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/package-info.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/database/v1/package-info.java @@ -26,6 +26,13 @@ * list backups for a database and to restore from an existing backup. * *

Sample for DatabaseAdminClient: + * + *

{@code
+ * try (DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create()) {
+ *   DatabaseName name = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
+ *   Database response = databaseAdminClient.getDatabase(name);
+ * }
+ * }
*/ @Generated("by gapic-generator-java") package com.google.cloud.spanner.admin.database.v1; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/InstanceAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/InstanceAdminClient.java index 6494e0eac90..68c0be81db8 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/InstanceAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/InstanceAdminClient.java @@ -85,6 +85,13 @@ *

This class provides the ability to make remote calls to the backing service through method * calls that map to API methods. Sample code to get started: * + *

{@code
+ * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+ *   InstanceConfigName name = InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]");
+ *   InstanceConfig response = instanceAdminClient.getInstanceConfig(name);
+ * }
+ * }
+ * *

Note: close() needs to be called on the InstanceAdminClient object to clean up resources such * as threads. In the example above, try-with-resources is used, which automatically calls close(). * @@ -199,6 +206,17 @@ public final OperationsClient getOperationsClient() { /** * Lists the supported instance configurations for a given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ProjectName parent = ProjectName.of("[PROJECT]");
+   *   for (InstanceConfig element : instanceAdminClient.listInstanceConfigs(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The name of the project for which a list of supported instance * configurations is requested. Values are of the form `projects/<project>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -215,6 +233,17 @@ public final ListInstanceConfigsPagedResponse listInstanceConfigs(ProjectName pa /** * Lists the supported instance configurations for a given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String parent = ProjectName.of("[PROJECT]").toString();
+   *   for (InstanceConfig element : instanceAdminClient.listInstanceConfigs(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The name of the project for which a list of supported instance * configurations is requested. Values are of the form `projects/<project>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -229,6 +258,22 @@ public final ListInstanceConfigsPagedResponse listInstanceConfigs(String parent) /** * Lists the supported instance configurations for a given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ListInstanceConfigsRequest request =
+   *       ListInstanceConfigsRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   for (InstanceConfig element : instanceAdminClient.listInstanceConfigs(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -242,6 +287,23 @@ public final ListInstanceConfigsPagedResponse listInstanceConfigs( * Lists the supported instance configurations for a given project. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ListInstanceConfigsRequest request =
+   *       ListInstanceConfigsRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.listInstanceConfigsPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (InstanceConfig element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listInstanceConfigsPagedCallable() { @@ -253,6 +315,24 @@ public final ListInstanceConfigsPagedResponse listInstanceConfigs( * Lists the supported instance configurations for a given project. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   while (true) {
+   *     ListInstanceConfigsResponse response =
+   *         instanceAdminClient.listInstanceConfigsCallable().call(request);
+   *     for (InstanceConfig element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listInstanceConfigsCallable() { @@ -263,6 +343,15 @@ public final ListInstanceConfigsPagedResponse listInstanceConfigs( /** * Gets information about a particular instance configuration. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   InstanceConfigName name = InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]");
+   *   InstanceConfig response = instanceAdminClient.getInstanceConfig(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested instance configuration. Values are of the form * `projects/<project>/instanceConfigs/<config>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -279,6 +368,15 @@ public final InstanceConfig getInstanceConfig(InstanceConfigName name) { /** * Gets information about a particular instance configuration. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String name = InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]").toString();
+   *   InstanceConfig response = instanceAdminClient.getInstanceConfig(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested instance configuration. Values are of the form * `projects/<project>/instanceConfigs/<config>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -292,6 +390,18 @@ public final InstanceConfig getInstanceConfig(String name) { /** * Gets information about a particular instance configuration. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetInstanceConfigRequest request =
+   *       GetInstanceConfigRequest.newBuilder()
+   *           .setName(InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]").toString())
+   *           .build();
+   *   InstanceConfig response = instanceAdminClient.getInstanceConfig(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -304,6 +414,19 @@ public final InstanceConfig getInstanceConfig(GetInstanceConfigRequest request) * Gets information about a particular instance configuration. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetInstanceConfigRequest request =
+   *       GetInstanceConfigRequest.newBuilder()
+   *           .setName(InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]").toString())
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.getInstanceConfigCallable().futureCall(request);
+   *   // Do something.
+   *   InstanceConfig response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getInstanceConfigCallable() { return stub.getInstanceConfigCallable(); @@ -313,6 +436,17 @@ public final UnaryCallable getInstance /** * Lists all instances in the given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ProjectName parent = ProjectName.of("[PROJECT]");
+   *   for (Instance element : instanceAdminClient.listInstances(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The name of the project for which a list of instances is requested. * Values are of the form `projects/<project>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -329,6 +463,17 @@ public final ListInstancesPagedResponse listInstances(ProjectName parent) { /** * Lists all instances in the given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String parent = ProjectName.of("[PROJECT]").toString();
+   *   for (Instance element : instanceAdminClient.listInstances(parent).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param parent Required. The name of the project for which a list of instances is requested. * Values are of the form `projects/<project>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -342,6 +487,23 @@ public final ListInstancesPagedResponse listInstances(String parent) { /** * Lists all instances in the given project. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ListInstancesRequest request =
+   *       ListInstancesRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .setFilter("filter-1274492040")
+   *           .build();
+   *   for (Instance element : instanceAdminClient.listInstances(request).iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -354,6 +516,24 @@ public final ListInstancesPagedResponse listInstances(ListInstancesRequest reque * Lists all instances in the given project. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ListInstancesRequest request =
+   *       ListInstancesRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setPageSize(883849137)
+   *           .setPageToken("pageToken873572522")
+   *           .setFilter("filter-1274492040")
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.listInstancesPagedCallable().futureCall(request);
+   *   // Do something.
+   *   for (Instance element : future.get().iterateAll()) {
+   *     // doThingsWith(element);
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listInstancesPagedCallable() { @@ -365,6 +545,23 @@ public final ListInstancesPagedResponse listInstances(ListInstancesRequest reque * Lists all instances in the given project. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   while (true) {
+   *     ListInstancesResponse response = instanceAdminClient.listInstancesCallable().call(request);
+   *     for (Instance element : response.getResponsesList()) {
+   *       // doThingsWith(element);
+   *     }
+   *     String nextPageToken = response.getNextPageToken();
+   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
+   *       request = request.toBuilder().setPageToken(nextPageToken).build();
+   *     } else {
+   *       break;
+   *     }
+   *   }
+   * }
+   * }
*/ public final UnaryCallable listInstancesCallable() { return stub.listInstancesCallable(); @@ -374,6 +571,15 @@ public final UnaryCallable listInst /** * Gets information about a particular instance. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   InstanceName name = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   Instance response = instanceAdminClient.getInstance(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested instance. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -388,6 +594,15 @@ public final Instance getInstance(InstanceName name) { /** * Gets information about a particular instance. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String name = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   Instance response = instanceAdminClient.getInstance(name);
+   * }
+   * }
+ * * @param name Required. The name of the requested instance. Values are of the form * `projects/<project>/instances/<instance>`. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -401,6 +616,19 @@ public final Instance getInstance(String name) { /** * Gets information about a particular instance. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetInstanceRequest request =
+   *       GetInstanceRequest.newBuilder()
+   *           .setName(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFieldMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   Instance response = instanceAdminClient.getInstance(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -413,6 +641,19 @@ public final Instance getInstance(GetInstanceRequest request) { * Gets information about a particular instance. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetInstanceRequest request =
+   *       GetInstanceRequest.newBuilder()
+   *           .setName(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .setFieldMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = instanceAdminClient.getInstanceCallable().futureCall(request);
+   *   // Do something.
+   *   Instance response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getInstanceCallable() { return stub.getInstanceCallable(); @@ -450,6 +691,18 @@ public final UnaryCallable getInstanceCallable() { * [response][google.longrunning.Operation.response] field type is * [Instance][google.spanner.admin.instance.v1.Instance], if successful. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ProjectName parent = ProjectName.of("[PROJECT]");
+   *   String instanceId = "instanceId902024336";
+   *   Instance instance = Instance.newBuilder().build();
+   *   Instance response =
+   *       instanceAdminClient.createInstanceAsync(parent, instanceId, instance).get();
+   * }
+   * }
+ * * @param parent Required. The name of the project in which to create the instance. Values are of * the form `projects/<project>`. * @param instanceId Required. The ID of the instance to create. Valid identifiers are of the form @@ -501,6 +754,18 @@ public final OperationFuture createInstanceAsy * [response][google.longrunning.Operation.response] field type is * [Instance][google.spanner.admin.instance.v1.Instance], if successful. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String parent = ProjectName.of("[PROJECT]").toString();
+   *   String instanceId = "instanceId902024336";
+   *   Instance instance = Instance.newBuilder().build();
+   *   Instance response =
+   *       instanceAdminClient.createInstanceAsync(parent, instanceId, instance).get();
+   * }
+   * }
+ * * @param parent Required. The name of the project in which to create the instance. Values are of * the form `projects/<project>`. * @param instanceId Required. The ID of the instance to create. Valid identifiers are of the form @@ -552,6 +817,20 @@ public final OperationFuture createInstanceAsy * [response][google.longrunning.Operation.response] field type is * [Instance][google.spanner.admin.instance.v1.Instance], if successful. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   CreateInstanceRequest request =
+   *       CreateInstanceRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setInstanceId("instanceId902024336")
+   *           .setInstance(Instance.newBuilder().build())
+   *           .build();
+   *   Instance response = instanceAdminClient.createInstanceAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -593,6 +872,21 @@ public final OperationFuture createInstanceAsy * [Instance][google.spanner.admin.instance.v1.Instance], if successful. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   CreateInstanceRequest request =
+   *       CreateInstanceRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setInstanceId("instanceId902024336")
+   *           .setInstance(Instance.newBuilder().build())
+   *           .build();
+   *   OperationFuture future =
+   *       instanceAdminClient.createInstanceOperationCallable().futureCall(request);
+   *   // Do something.
+   *   Instance response = future.get();
+   * }
+   * }
*/ public final OperationCallable createInstanceOperationCallable() { @@ -632,6 +926,21 @@ public final OperationFuture createInstanceAsy * [Instance][google.spanner.admin.instance.v1.Instance], if successful. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   CreateInstanceRequest request =
+   *       CreateInstanceRequest.newBuilder()
+   *           .setParent(ProjectName.of("[PROJECT]").toString())
+   *           .setInstanceId("instanceId902024336")
+   *           .setInstance(Instance.newBuilder().build())
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.createInstanceCallable().futureCall(request);
+   *   // Do something.
+   *   Operation response = future.get();
+   * }
+   * }
*/ public final UnaryCallable createInstanceCallable() { return stub.createInstanceCallable(); @@ -673,6 +982,16 @@ public final UnaryCallable createInstanceCalla *

Authorization requires `spanner.instances.update` permission on resource * [name][google.spanner.admin.instance.v1.Instance.name]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   Instance instance = Instance.newBuilder().build();
+   *   FieldMask fieldMask = FieldMask.newBuilder().build();
+   *   Instance response = instanceAdminClient.updateInstanceAsync(instance, fieldMask).get();
+   * }
+   * }
+ * * @param instance Required. The instance to update, which must always include the instance name. * Otherwise, only fields mentioned in * [field_mask][google.spanner.admin.instance.v1.UpdateInstanceRequest.field_mask] need be @@ -727,6 +1046,19 @@ public final OperationFuture updateInstanceAsy *

Authorization requires `spanner.instances.update` permission on resource * [name][google.spanner.admin.instance.v1.Instance.name]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   UpdateInstanceRequest request =
+   *       UpdateInstanceRequest.newBuilder()
+   *           .setInstance(Instance.newBuilder().build())
+   *           .setFieldMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   Instance response = instanceAdminClient.updateInstanceAsync(request).get();
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -772,6 +1104,20 @@ public final OperationFuture updateInstanceAsy * [name][google.spanner.admin.instance.v1.Instance.name]. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   UpdateInstanceRequest request =
+   *       UpdateInstanceRequest.newBuilder()
+   *           .setInstance(Instance.newBuilder().build())
+   *           .setFieldMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   OperationFuture future =
+   *       instanceAdminClient.updateInstanceOperationCallable().futureCall(request);
+   *   // Do something.
+   *   Instance response = future.get();
+   * }
+   * }
*/ public final OperationCallable updateInstanceOperationCallable() { @@ -815,6 +1161,20 @@ public final OperationFuture updateInstanceAsy * [name][google.spanner.admin.instance.v1.Instance.name]. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   UpdateInstanceRequest request =
+   *       UpdateInstanceRequest.newBuilder()
+   *           .setInstance(Instance.newBuilder().build())
+   *           .setFieldMask(FieldMask.newBuilder().build())
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.updateInstanceCallable().futureCall(request);
+   *   // Do something.
+   *   Operation response = future.get();
+   * }
+   * }
*/ public final UnaryCallable updateInstanceCallable() { return stub.updateInstanceCallable(); @@ -833,6 +1193,15 @@ public final UnaryCallable updateInstanceCalla *

* The instance and *all of its databases* immediately and irrevocably disappear * from the API. All data in the databases is permanently deleted. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   InstanceName name = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   *   instanceAdminClient.deleteInstance(name);
+   * }
+   * }
+ * * @param name Required. The name of the instance to be deleted. Values are of the form * `projects/<project>/instances/<instance>` * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -856,6 +1225,15 @@ public final void deleteInstance(InstanceName name) { *

* The instance and *all of its databases* immediately and irrevocably disappear * from the API. All data in the databases is permanently deleted. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String name = InstanceName.of("[PROJECT]", "[INSTANCE]").toString();
+   *   instanceAdminClient.deleteInstance(name);
+   * }
+   * }
+ * * @param name Required. The name of the instance to be deleted. Values are of the form * `projects/<project>/instances/<instance>` * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -878,6 +1256,18 @@ public final void deleteInstance(String name) { *

* The instance and *all of its databases* immediately and irrevocably disappear * from the API. All data in the databases is permanently deleted. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   DeleteInstanceRequest request =
+   *       DeleteInstanceRequest.newBuilder()
+   *           .setName(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .build();
+   *   instanceAdminClient.deleteInstance(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -899,6 +1289,18 @@ public final void deleteInstance(DeleteInstanceRequest request) { * from the API. All data in the databases is permanently deleted. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   DeleteInstanceRequest request =
+   *       DeleteInstanceRequest.newBuilder()
+   *           .setName(InstanceName.of("[PROJECT]", "[INSTANCE]").toString())
+   *           .build();
+   *   ApiFuture future = instanceAdminClient.deleteInstanceCallable().futureCall(request);
+   *   // Do something.
+   *   future.get();
+   * }
+   * }
*/ public final UnaryCallable deleteInstanceCallable() { return stub.deleteInstanceCallable(); @@ -911,6 +1313,16 @@ public final UnaryCallable deleteInstanceCallable( *

Authorization requires `spanner.instances.setIamPolicy` on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ResourceName resource = ProjectName.of("[PROJECT]");
+   *   Policy policy = Policy.newBuilder().build();
+   *   Policy response = instanceAdminClient.setIamPolicy(resource, policy);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being specified. See the * operation documentation for the appropriate value for this field. * @param policy REQUIRED: The complete policy to be applied to the `resource`. The size of the @@ -934,6 +1346,16 @@ public final Policy setIamPolicy(ResourceName resource, Policy policy) { *

Authorization requires `spanner.instances.setIamPolicy` on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String resource = ProjectName.of("[PROJECT]").toString();
+   *   Policy policy = Policy.newBuilder().build();
+   *   Policy response = instanceAdminClient.setIamPolicy(resource, policy);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being specified. See the * operation documentation for the appropriate value for this field. * @param policy REQUIRED: The complete policy to be applied to the `resource`. The size of the @@ -954,6 +1376,19 @@ public final Policy setIamPolicy(String resource, Policy policy) { *

Authorization requires `spanner.instances.setIamPolicy` on * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   SetIamPolicyRequest request =
+   *       SetIamPolicyRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .setPolicy(Policy.newBuilder().build())
+   *           .build();
+   *   Policy response = instanceAdminClient.setIamPolicy(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -969,6 +1404,19 @@ public final Policy setIamPolicy(SetIamPolicyRequest request) { * [resource][google.iam.v1.SetIamPolicyRequest.resource]. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   SetIamPolicyRequest request =
+   *       SetIamPolicyRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .setPolicy(Policy.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = instanceAdminClient.setIamPolicyCallable().futureCall(request);
+   *   // Do something.
+   *   Policy response = future.get();
+   * }
+   * }
*/ public final UnaryCallable setIamPolicyCallable() { return stub.setIamPolicyCallable(); @@ -982,6 +1430,15 @@ public final UnaryCallable setIamPolicyCallable() { *

Authorization requires `spanner.instances.getIamPolicy` on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ResourceName resource = ProjectName.of("[PROJECT]");
+   *   Policy response = instanceAdminClient.getIamPolicy(resource);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being requested. See the * operation documentation for the appropriate value for this field. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1002,6 +1459,15 @@ public final Policy getIamPolicy(ResourceName resource) { *

Authorization requires `spanner.instances.getIamPolicy` on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String resource = ProjectName.of("[PROJECT]").toString();
+   *   Policy response = instanceAdminClient.getIamPolicy(resource);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy is being requested. See the * operation documentation for the appropriate value for this field. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -1019,6 +1485,19 @@ public final Policy getIamPolicy(String resource) { *

Authorization requires `spanner.instances.getIamPolicy` on * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetIamPolicyRequest request =
+   *       GetIamPolicyRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .setOptions(GetPolicyOptions.newBuilder().build())
+   *           .build();
+   *   Policy response = instanceAdminClient.getIamPolicy(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1035,6 +1514,19 @@ public final Policy getIamPolicy(GetIamPolicyRequest request) { * [resource][google.iam.v1.GetIamPolicyRequest.resource]. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   GetIamPolicyRequest request =
+   *       GetIamPolicyRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .setOptions(GetPolicyOptions.newBuilder().build())
+   *           .build();
+   *   ApiFuture future = instanceAdminClient.getIamPolicyCallable().futureCall(request);
+   *   // Do something.
+   *   Policy response = future.get();
+   * }
+   * }
*/ public final UnaryCallable getIamPolicyCallable() { return stub.getIamPolicyCallable(); @@ -1048,6 +1540,17 @@ public final UnaryCallable getIamPolicyCallable() { * NOT_FOUND error if the user has `spanner.instances.list` permission on the containing Google * Cloud Project. Otherwise returns an empty set of permissions. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   ResourceName resource = ProjectName.of("[PROJECT]");
+   *   List permissions = new ArrayList<>();
+   *   TestIamPermissionsResponse response =
+   *       instanceAdminClient.testIamPermissions(resource, permissions);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy detail is being requested. See the * operation documentation for the appropriate value for this field. * @param permissions The set of permissions to check for the `resource`. Permissions with @@ -1073,6 +1576,17 @@ public final TestIamPermissionsResponse testIamPermissions( * NOT_FOUND error if the user has `spanner.instances.list` permission on the containing Google * Cloud Project. Otherwise returns an empty set of permissions. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   String resource = ProjectName.of("[PROJECT]").toString();
+   *   List permissions = new ArrayList<>();
+   *   TestIamPermissionsResponse response =
+   *       instanceAdminClient.testIamPermissions(resource, permissions);
+   * }
+   * }
+ * * @param resource REQUIRED: The resource for which the policy detail is being requested. See the * operation documentation for the appropriate value for this field. * @param permissions The set of permissions to check for the `resource`. Permissions with @@ -1098,6 +1612,19 @@ public final TestIamPermissionsResponse testIamPermissions( * NOT_FOUND error if the user has `spanner.instances.list` permission on the containing Google * Cloud Project. Otherwise returns an empty set of permissions. * + *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   TestIamPermissionsRequest request =
+   *       TestIamPermissionsRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .addAllPermissions(new ArrayList())
+   *           .build();
+   *   TestIamPermissionsResponse response = instanceAdminClient.testIamPermissions(request);
+   * }
+   * }
+ * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1114,6 +1641,20 @@ public final TestIamPermissionsResponse testIamPermissions(TestIamPermissionsReq * Cloud Project. Otherwise returns an empty set of permissions. * *

Sample code: + * + *

{@code
+   * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+   *   TestIamPermissionsRequest request =
+   *       TestIamPermissionsRequest.newBuilder()
+   *           .setResource(ProjectName.of("[PROJECT]").toString())
+   *           .addAllPermissions(new ArrayList())
+   *           .build();
+   *   ApiFuture future =
+   *       instanceAdminClient.testIamPermissionsCallable().futureCall(request);
+   *   // Do something.
+   *   TestIamPermissionsResponse response = future.get();
+   * }
+   * }
*/ public final UnaryCallable testIamPermissionsCallable() { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/package-info.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/package-info.java index 189e53a2d60..19b1d13aca7 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/package-info.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/admin/instance/v1/package-info.java @@ -38,6 +38,13 @@ * databases in that instance, and their performance may suffer. * *

Sample for InstanceAdminClient: + * + *

{@code
+ * try (InstanceAdminClient instanceAdminClient = InstanceAdminClient.create()) {
+ *   InstanceConfigName name = InstanceConfigName.of("[PROJECT]", "[INSTANCE_CONFIG]");
+ *   InstanceConfig response = instanceAdminClient.getInstanceConfig(name);
+ * }
+ * }
*/ @Generated("by gapic-generator-java") package com.google.cloud.spanner.admin.instance.v1; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementExecutor.java index 24681939187..7ec09766697 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementExecutor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementExecutor.java @@ -16,8 +16,6 @@ package com.google.cloud.spanner.connection; -import com.google.cloud.spanner.connection.ClientSideStatementImpl.CompileException; - /** * A {@link ClientSideStatementExecutor} is used to compile {@link ClientSideStatement}s from the * json source file, and to execute these against a {@link Connection} (through a {@link @@ -25,17 +23,6 @@ */ interface ClientSideStatementExecutor { - /** - * Compiles the given {@link ClientSideStatementImpl} and registers this statement with this - * executor. A statement must be compiled before it can be executed. The parser automatically - * compiles all available statements during initialization. - * - * @param statement The statement to compile. - * @throws CompileException If the statement could not be compiled. This should never happen, as - * it would indicate that an invalid statement has been defined in the source file. - */ - void compile(ClientSideStatementImpl statement) throws CompileException; - /** * Executes the {@link ClientSideStatementImpl} that has been compiled and registered with this * executor on the specified connection. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java index 7621a0ced5c..e9c9c1654e9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java @@ -19,6 +19,7 @@ import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.common.base.Preconditions; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.List; @@ -143,10 +144,12 @@ public String getMessage() { ClientSideStatementImpl compile() throws CompileException { try { this.pattern = Pattern.compile(regex); - this.executor = - (ClientSideStatementExecutor) - Class.forName(getClass().getPackage().getName() + "." + executorName).newInstance(); - this.executor.compile(this); + @SuppressWarnings("unchecked") + Constructor constructor = + (Constructor) + Class.forName(getClass().getPackage().getName() + "." + executorName) + .getDeclaredConstructor(ClientSideStatementImpl.class); + this.executor = constructor.newInstance(this); return this; } catch (Exception e) { throw new CompileException(e, this); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementNoParamExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementNoParamExecutor.java index 4c31e3e28d2..6f5e683ce64 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementNoParamExecutor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementNoParamExecutor.java @@ -24,12 +24,16 @@ * SHOW AUTOCOMMIT. The executor just calls a method with no parameters. */ class ClientSideStatementNoParamExecutor implements ClientSideStatementExecutor { - private Method method; + private final Method method; - ClientSideStatementNoParamExecutor() {} - - @Override - public void compile(ClientSideStatementImpl statement) throws CompileException { + /** + * Creates and compiles the given {@link ClientSideStatementImpl}. + * + * @param statement The statement to compile. + * @throws CompileException If the statement could not be compiled. This should never happen, as + * it would indicate that an invalid statement has been defined in the source file. + */ + ClientSideStatementNoParamExecutor(ClientSideStatementImpl statement) throws CompileException { try { this.method = ConnectionStatementExecutor.class.getDeclaredMethod(statement.getMethodName()); } catch (NoSuchMethodException | SecurityException e) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementSetExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementSetExecutor.java index 54765282f5c..4f4fe140428 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementSetExecutor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementSetExecutor.java @@ -30,14 +30,20 @@ * AUTOCOMMIT=TRUE. */ class ClientSideStatementSetExecutor implements ClientSideStatementExecutor { - private ClientSideStatementImpl statement; - private Method method; - private ClientSideStatementValueConverter converter; - private Pattern allowedValuesPattern; + private final ClientSideStatementImpl statement; + private final Method method; + private final ClientSideStatementValueConverter converter; + private final Pattern allowedValuesPattern; + /** + * Creates and compiles the given {@link ClientSideStatementImpl}. + * + * @param statement The statement to compile. + * @throws CompileException If the statement could not be compiled. This should never happen, as + * it would indicate that an invalid statement has been defined in the source file. + */ @SuppressWarnings("unchecked") - @Override - public void compile(ClientSideStatementImpl statement) throws CompileException { + ClientSideStatementSetExecutor(ClientSideStatementImpl statement) throws CompileException { Preconditions.checkNotNull(statement.getSetStatement()); try { this.statement = statement; 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 b49adbf1245..0c91e8060e7 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 @@ -259,11 +259,17 @@ public void close() { // Ignore as we are closing the connection. } } - statementExecutor.shutdownNow(); - spannerPool.removeConnection(options, this); + // Try to wait for the current statement to finish (if any) before we actually close the + // connection. + this.closed = true; + statementExecutor.shutdown(); leakedException = null; + spannerPool.removeConnection(options, this); + statementExecutor.awaitTermination(10L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + // ignore and continue to close the connection. } finally { - this.closed = true; + statementExecutor.shutdownNow(); } } } 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 a668342f1bf..a688454f303 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 @@ -150,6 +150,8 @@ public String[] getValidValues() { static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true; private static final String DEFAULT_CREDENTIALS = null; private static final String DEFAULT_OAUTH_TOKEN = null; + private static final String DEFAULT_MIN_SESSIONS = null; + private static final String DEFAULT_MAX_SESSIONS = null; private static final String DEFAULT_NUM_CHANNELS = null; private static final String DEFAULT_USER_AGENT = null; private static final String DEFAULT_OPTIMIZER_VERSION = ""; @@ -172,6 +174,10 @@ public String[] getValidValues() { * OAuth token to use for authentication. Cannot be used in combination with a credentials file. */ public static final String OAUTH_TOKEN_PROPERTY_NAME = "oauthToken"; + /** Name of the 'minSessions' connection property. */ + public static final String MIN_SESSIONS_PROPERTY_NAME = "minSessions"; + /** Name of the 'numChannels' connection property. */ + public static final String MAX_SESSIONS_PROPERTY_NAME = "maxSessions"; /** Name of the 'numChannels' connection property. */ public static final String NUM_CHANNELS_PROPERTY_NAME = "numChannels"; /** Custom user agent string is only for other Google libraries. */ @@ -204,6 +210,12 @@ public String[] getValidValues() { ConnectionProperty.createStringProperty( OAUTH_TOKEN_PROPERTY_NAME, "A valid pre-existing OAuth token to use for authentication for this connection. Setting this property will take precedence over any value set for a credentials file."), + ConnectionProperty.createStringProperty( + MIN_SESSIONS_PROPERTY_NAME, + "The minimum number of sessions in the backing session pool. The default is 100."), + ConnectionProperty.createStringProperty( + MAX_SESSIONS_PROPERTY_NAME, + "The maximum number of sessions in the backing session pool. The default is 400."), ConnectionProperty.createStringProperty( NUM_CHANNELS_PROPERTY_NAME, "The number of gRPC channels to use to communicate with Cloud Spanner. The default is 4."), @@ -327,6 +339,9 @@ private boolean isValidUri(String uri) { * true. *
  • readonly (boolean): Sets the initial readonly mode for the connection. Default is * false. + *
  • minSessions (int): Sets the minimum number of sessions in the backing session pool. + *
  • maxSessions (int): Sets the maximum number of sessions in the backing session pool. + *
  • numChannels (int): Sets the number of gRPC channels to use for the connection. *
  • retryAbortsInternally (boolean): Sets the initial retryAbortsInternally mode for the * connection. Default is true. *
  • optimizerVersion (string): Sets the query optimizer version to use for the connection. @@ -437,6 +452,8 @@ public static Builder newBuilder() { private final Credentials credentials; private final SessionPoolOptions sessionPoolOptions; private final Integer numChannels; + private final Integer minSessions; + private final Integer maxSessions; private final String userAgent; private final QueryOptions queryOptions; @@ -453,7 +470,6 @@ private ConnectionOptions(Builder builder) { this.warnings = checkValidProperties(builder.uri); this.uri = builder.uri; - this.sessionPoolOptions = builder.sessionPoolOptions; this.credentialsUrl = builder.credentialsUrl != null ? builder.credentialsUrl : parseCredentials(builder.uri); this.oauthToken = @@ -492,19 +508,12 @@ private ConnectionOptions(Builder builder) { } else { this.credentials = getCredentialsService().createCredentials(this.credentialsUrl); } - String numChannelsValue = parseNumChannels(builder.uri); - if (numChannelsValue != null) { - try { - this.numChannels = Integer.valueOf(numChannelsValue); - } catch (NumberFormatException e) { - throw SpannerExceptionFactory.newSpannerException( - ErrorCode.INVALID_ARGUMENT, - "Invalid numChannels value specified: " + numChannelsValue, - e); - } - } else { - this.numChannels = null; - } + this.minSessions = + parseIntegerProperty(MIN_SESSIONS_PROPERTY_NAME, parseMinSessions(builder.uri)); + this.maxSessions = + parseIntegerProperty(MAX_SESSIONS_PROPERTY_NAME, parseMaxSessions(builder.uri)); + this.numChannels = + parseIntegerProperty(NUM_CHANNELS_PROPERTY_NAME, parseNumChannels(builder.uri)); String projectId = matcher.group(Builder.PROJECT_GROUP); if (Builder.DEFAULT_PROJECT_ID_PLACEHOLDER.equalsIgnoreCase(projectId)) { @@ -518,6 +527,36 @@ private ConnectionOptions(Builder builder) { this.statementExecutionInterceptors = Collections.unmodifiableList(builder.statementExecutionInterceptors); this.configurator = builder.configurator; + + if (this.minSessions != null || this.maxSessions != null) { + SessionPoolOptions.Builder sessionPoolOptionsBuilder = + builder.sessionPoolOptions == null + ? SessionPoolOptions.newBuilder() + : builder.sessionPoolOptions.toBuilder(); + if (this.minSessions != null) { + sessionPoolOptionsBuilder.setMinSessions(this.minSessions); + } + if (this.maxSessions != null) { + sessionPoolOptionsBuilder.setMaxSessions(this.maxSessions); + } + this.sessionPoolOptions = sessionPoolOptionsBuilder.build(); + } else { + this.sessionPoolOptions = builder.sessionPoolOptions; + } + } + + private static Integer parseIntegerProperty(String propertyName, String value) { + if (value != null) { + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + throw SpannerExceptionFactory.newSpannerException( + ErrorCode.INVALID_ARGUMENT, + String.format("Invalid %s value specified: %s", propertyName, value), + e); + } + } + return null; } SpannerOptionsConfigurator getConfigurator() { @@ -565,6 +604,18 @@ static String parseOAuthToken(String uri) { return value != null ? value : DEFAULT_OAUTH_TOKEN; } + @VisibleForTesting + static String parseMinSessions(String uri) { + String value = parseUriProperty(uri, MIN_SESSIONS_PROPERTY_NAME); + return value != null ? value : DEFAULT_MIN_SESSIONS; + } + + @VisibleForTesting + static String parseMaxSessions(String uri) { + String value = parseUriProperty(uri, MAX_SESSIONS_PROPERTY_NAME); + return value != null ? value : DEFAULT_MAX_SESSIONS; + } + @VisibleForTesting static String parseNumChannels(String uri) { String value = parseUriProperty(uri, NUM_CHANNELS_PROPERTY_NAME); @@ -671,6 +722,24 @@ public SessionPoolOptions getSessionPoolOptions() { return sessionPoolOptions; } + /** + * The minimum number of sessions in the backing session pool of this connection. The session pool + * is shared between all connections in the same JVM that connect to the same Cloud Spanner + * database using the same connection settings. + */ + public Integer getMinSessions() { + return minSessions; + } + + /** + * The maximum number of sessions in the backing session pool of this connection. The session pool + * is shared between all connections in the same JVM that connect to the same Cloud Spanner + * database using the same connection settings. + */ + public Integer getMaxSessions() { + return maxSessions; + } + /** The number of channels to use for the connection. */ public Integer getNumChannels() { return numChannels; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java index 52011eb9100..edabb7f0177 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java @@ -33,7 +33,6 @@ import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.TimestampBound; import com.google.cloud.spanner.TransactionContext; -import com.google.cloud.spanner.TransactionManager; import com.google.cloud.spanner.TransactionRunner; import com.google.cloud.spanner.TransactionRunner.TransactionCallable; import com.google.cloud.spanner.connection.StatementParser.ParsedStatement; @@ -71,7 +70,6 @@ class SingleUseTransaction extends AbstractBaseUnitOfWork { private final TimestampBound readOnlyStaleness; private final AutocommitDmlMode autocommitDmlMode; private volatile SettableApiFuture readTimestamp = null; - private volatile TransactionManager txManager; private volatile TransactionRunner writeTransaction; private boolean used = false; private volatile UnitOfWorkState state = UnitOfWorkState.STARTED; @@ -221,29 +219,21 @@ public Timestamp getReadTimestampOrNull() { } private boolean hasCommitTimestamp() { - return state == UnitOfWorkState.COMMITTED - && (writeTransaction != null - || (txManager != null - && txManager.getState() - == com.google.cloud.spanner.TransactionManager.TransactionState.COMMITTED)); + return state == UnitOfWorkState.COMMITTED && writeTransaction != null; } @Override public Timestamp getCommitTimestamp() { ConnectionPreconditions.checkState( hasCommitTimestamp(), "There is no commit timestamp available for this transaction."); - return writeTransaction != null - ? writeTransaction.getCommitTimestamp() - : txManager.getCommitTimestamp(); + return writeTransaction.getCommitTimestamp(); } @Override public Timestamp getCommitTimestampOrNull() { if (hasCommitTimestamp()) { try { - return writeTransaction != null - ? writeTransaction.getCommitTimestamp() - : txManager.getCommitTimestamp(); + return writeTransaction.getCommitTimestamp(); } catch (SpannerException e) { // ignore } 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 de351c87c93..51bfde8f00e 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 @@ -25,6 +25,7 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; @@ -62,6 +63,15 @@ public class SpannerPool { private static final String CONNECTION_API_CLIENT_LIB_TOKEN = "sp-jdbc"; private static final Logger logger = Logger.getLogger(SpannerPool.class.getName()); + private static final Function DEFAULT_CLOSE_FUNCTION = + new Function() { + @Override + public Void apply(Spanner spanner) { + spanner.close(); + return null; + } + }; + /** * Closes the default {@link SpannerPool} and all {@link Spanner} instances that have been opened * by connections and that are still open. Call this method at the end of your application to @@ -148,7 +158,8 @@ static class SpannerPoolKey { private final boolean usePlainText; private final String userAgent; - private static SpannerPoolKey of(ConnectionOptions options) { + @VisibleForTesting + static SpannerPoolKey of(ConnectionOptions options) { return new SpannerPoolKey(options); } @@ -156,7 +167,10 @@ private SpannerPoolKey(ConnectionOptions options) { this.host = options.getHost(); this.projectId = options.getProjectId(); this.credentialsKey = CredentialsKey.create(options); - this.sessionPoolOptions = options.getSessionPoolOptions(); + this.sessionPoolOptions = + options.getSessionPoolOptions() == null + ? SessionPoolOptions.newBuilder().build() + : options.getSessionPoolOptions(); this.numChannels = options.getNumChannels(); this.usePlainText = options.isUsePlainText(); this.userAgent = options.getUserAgent(); @@ -391,6 +405,12 @@ void checkAndCloseSpanners() { @VisibleForTesting void checkAndCloseSpanners(CheckAndCloseSpannersMode mode) { + checkAndCloseSpanners(mode, DEFAULT_CLOSE_FUNCTION); + } + + @VisibleForTesting + void checkAndCloseSpanners( + CheckAndCloseSpannersMode mode, Function closeSpannerFunction) { List keysStillInUse = new ArrayList<>(); synchronized (this) { for (Entry entry : spanners.entrySet()) { @@ -412,7 +432,7 @@ void checkAndCloseSpanners(CheckAndCloseSpannersMode mode) { // Force close all Spanner instances by passing in a value that will always be less than // the // difference between the current time and the close time of a connection. - closeUnusedSpanners(Long.MIN_VALUE); + closeUnusedSpanners(Long.MIN_VALUE, closeSpannerFunction); } else { logLeakedConnections(keysStillInUse); throw SpannerExceptionFactory.newSpannerException( @@ -452,6 +472,11 @@ private void logLeakedConnections(List keysStillInUse) { */ @VisibleForTesting void closeUnusedSpanners(long closeSpannerAfterMillisecondsUnused) { + closeUnusedSpanners(closeSpannerAfterMillisecondsUnused, DEFAULT_CLOSE_FUNCTION); + } + + void closeUnusedSpanners( + long closeSpannerAfterMillisecondsUnused, Function closeSpannerFunction) { List keysToBeRemoved = new ArrayList<>(); synchronized (this) { for (Entry entry : lastConnectionClosedAt.entrySet()) { @@ -465,7 +490,9 @@ void closeUnusedSpanners(long closeSpannerAfterMillisecondsUnused) { Spanner spanner = spanners.get(entry.getKey()); if (spanner != null) { try { - spanner.close(); + closeSpannerFunction.apply(spanner); + } catch (Throwable t) { + // Ignore any errors and continue with the next one in the pool. } finally { // Even if the close operation failed, we should remove the spanner object as it is no // longer valid. @@ -480,11 +507,4 @@ void closeUnusedSpanners(long closeSpannerAfterMillisecondsUnused) { } } } - - @VisibleForTesting - int getCurrentSpannerCount() { - synchronized (this) { - return spanners.size(); - } - } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java index baaadbe1676..131533a3374 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementExecutor.java @@ -164,6 +164,14 @@ private static ListeningExecutorService createExecutorService() { this.interceptors = Collections.unmodifiableList(interceptors); } + void shutdown() { + executor.shutdown(); + } + + void awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + executor.awaitTermination(timeout, unit); + } + /** * Shutdown this executor now and do not wait for any statement that is being executed to finish. */ diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 5172fe448fb..608d9e23d53 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -77,7 +77,6 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -161,6 +160,7 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -244,6 +244,8 @@ private void awaitTermination() throws InterruptedException { private static final int GRPC_KEEPALIVE_SECONDS = 2 * 60; private static final String USER_AGENT_KEY = "user-agent"; private static final String CLIENT_LIBRARY_LANGUAGE = "spanner-java"; + public static final String DEFAULT_USER_AGENT = + CLIENT_LIBRARY_LANGUAGE + "/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class); private final ManagedInstantiatingExecutorProvider executorProvider; private boolean rpcIsClosed; @@ -305,18 +307,11 @@ public GapicSpannerRpc(final SpannerOptions options) { GaxGrpcProperties.getGrpcTokenName(), GaxGrpcProperties.getGrpcVersion()) .build(); - HeaderProvider mergedHeaderProvider = options.getMergedHeaderProvider(internalHeaderProvider); - Map headersWithUserAgent = - ImmutableMap.builder() - .put( - USER_AGENT_KEY, - CLIENT_LIBRARY_LANGUAGE - + "/" - + GaxProperties.getLibraryVersion(GapicSpannerRpc.class)) - .putAll(mergedHeaderProvider.getHeaders()) - .build(); + final HeaderProvider mergedHeaderProvider = + options.getMergedHeaderProvider(internalHeaderProvider); final HeaderProvider headerProviderWithUserAgent = - FixedHeaderProvider.create(headersWithUserAgent); + headerProviderWithUserAgentFrom(mergedHeaderProvider); + this.metadataProvider = SpannerMetadataProvider.create( headerProviderWithUserAgent.getHeaders(), @@ -494,6 +489,16 @@ public UnaryCallable createUnaryCalla } } + private static HeaderProvider headerProviderWithUserAgentFrom(HeaderProvider headerProvider) { + final Map headersWithUserAgent = new HashMap<>(headerProvider.getHeaders()); + final String userAgent = headersWithUserAgent.get(USER_AGENT_KEY); + headersWithUserAgent.put( + USER_AGENT_KEY, + userAgent == null ? DEFAULT_USER_AGENT : userAgent + " " + DEFAULT_USER_AGENT); + + return FixedHeaderProvider.create(headersWithUserAgent); + } + private static void checkEmulatorConnection( SpannerOptions options, TransportChannelProvider channelProvider, @@ -1718,6 +1723,22 @@ public void shutdown() { } } + public void shutdownNow() { + this.rpcIsClosed = true; + this.spannerStub.close(); + this.partitionedDmlStub.close(); + this.instanceAdminStub.close(); + this.databaseAdminStub.close(); + this.spannerWatchdog.shutdown(); + this.executorProvider.shutdown(); + + this.spannerStub.shutdownNow(); + this.partitionedDmlStub.shutdownNow(); + this.instanceAdminStub.shutdownNow(); + this.databaseAdminStub.shutdownNow(); + this.spannerWatchdog.shutdownNow(); + } + @Override public boolean isClosed() { return rpcIsClosed; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java index ef979a87060..6d2fe90227c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.List; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,6 +46,8 @@ public class RemoteSpannerHelper { private final InstanceId instanceId; private static int dbSeq; private static int dbPrefix = new Random().nextInt(Integer.MAX_VALUE); + private static final AtomicInteger backupSeq = new AtomicInteger(); + private static int backupPrefix = new Random().nextInt(Integer.MAX_VALUE); private final List dbs = new ArrayList<>(); protected RemoteSpannerHelper(SpannerOptions options, InstanceId instanceId, Spanner client) { @@ -100,6 +103,13 @@ public String getUniqueDatabaseId() { return String.format("testdb_%d_%04d", dbPrefix, dbSeq++); } + /** + * Returns a backup id which is guaranteed to be unique within the context of this environment. + */ + public String getUniqueBackupId() { + return String.format("testbck_%06d_%04d", backupPrefix, backupSeq.incrementAndGet()); + } + /** * Creates a test database defined by {@code statements} in the test instance. A {@code CREATE * DATABASE ...} statement should not be included; an appropriate name will be chosen and the diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java new file mode 100644 index 00000000000..021adf4dd7f --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java @@ -0,0 +1,41 @@ +/* + * 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.google.cloud.spanner.testing; + +import com.google.cloud.Timestamp; +import java.util.concurrent.TimeUnit; + +public class TimestampHelper { + + public static Timestamp daysAgo(int days) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + - TimeUnit.MICROSECONDS.convert(days, TimeUnit.DAYS)); + } + + public static Timestamp afterDays(int days) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + + TimeUnit.MICROSECONDS.convert(days, TimeUnit.DAYS)); + } + + public static Timestamp afterMinutes(int minutes) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + + TimeUnit.MICROSECONDS.convert(minutes, TimeUnit.MINUTES)); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/SpannerClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/SpannerClient.java index 46b124968cd..c05c3d5ee9e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/SpannerClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/SpannerClient.java @@ -73,6 +73,13 @@ *

    This class provides the ability to make remote calls to the backing service through method * calls that map to API methods. Sample code to get started: * + *

    {@code
    + * try (SpannerClient spannerClient = SpannerClient.create()) {
    + *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
    + *   Session response = spannerClient.createSession(database);
    + * }
    + * }
    + * *

    Note: close() needs to be called on the SpannerClient object to clean up resources such as * threads. In the example above, try-with-resources is used, which automatically calls close(). * @@ -187,6 +194,15 @@ public SpannerStub getStub() { *

    Idle sessions can be kept alive by sending a trivial SQL query periodically, e.g., `"SELECT * 1"`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
    +   *   Session response = spannerClient.createSession(database);
    +   * }
    +   * }
    + * * @param database Required. The database in which the new session is created. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -216,6 +232,15 @@ public final Session createSession(DatabaseName database) { *

    Idle sessions can be kept alive by sending a trivial SQL query periodically, e.g., `"SELECT * 1"`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
    +   *   Session response = spannerClient.createSession(database);
    +   * }
    +   * }
    + * * @param database Required. The database in which the new session is created. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -242,6 +267,19 @@ public final Session createSession(String database) { *

    Idle sessions can be kept alive by sending a trivial SQL query periodically, e.g., `"SELECT * 1"`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   CreateSessionRequest request =
    +   *       CreateSessionRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setSession(Session.newBuilder().build())
    +   *           .build();
    +   *   Session response = spannerClient.createSession(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -268,6 +306,19 @@ public final Session createSession(CreateSessionRequest request) { * 1"`. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   CreateSessionRequest request =
    +   *       CreateSessionRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setSession(Session.newBuilder().build())
    +   *           .build();
    +   *   ApiFuture future = spannerClient.createSessionCallable().futureCall(request);
    +   *   // Do something.
    +   *   Session response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable createSessionCallable() { return stub.createSessionCallable(); @@ -280,6 +331,17 @@ public final UnaryCallable createSessionCallable( *

    This API can be used to initialize a session cache on the clients. See https://goo.gl/TgSFN2 * for best practices on session cache management. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
    +   *   int sessionCount = 185691686;
    +   *   BatchCreateSessionsResponse response =
    +   *       spannerClient.batchCreateSessions(database, sessionCount);
    +   * }
    +   * }
    + * * @param database Required. The database in which the new sessions are created. * @param sessionCount Required. The number of sessions to be created in this batch call. The API * may return fewer than the requested number of sessions. If a specific number of sessions @@ -304,6 +366,17 @@ public final BatchCreateSessionsResponse batchCreateSessions( *

    This API can be used to initialize a session cache on the clients. See https://goo.gl/TgSFN2 * for best practices on session cache management. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
    +   *   int sessionCount = 185691686;
    +   *   BatchCreateSessionsResponse response =
    +   *       spannerClient.batchCreateSessions(database, sessionCount);
    +   * }
    +   * }
    + * * @param database Required. The database in which the new sessions are created. * @param sessionCount Required. The number of sessions to be created in this batch call. The API * may return fewer than the requested number of sessions. If a specific number of sessions @@ -327,6 +400,20 @@ public final BatchCreateSessionsResponse batchCreateSessions(String database, in *

    This API can be used to initialize a session cache on the clients. See https://goo.gl/TgSFN2 * for best practices on session cache management. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   BatchCreateSessionsRequest request =
    +   *       BatchCreateSessionsRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setSessionTemplate(Session.newBuilder().build())
    +   *           .setSessionCount(185691686)
    +   *           .build();
    +   *   BatchCreateSessionsResponse response = spannerClient.batchCreateSessions(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -342,6 +429,21 @@ public final BatchCreateSessionsResponse batchCreateSessions(BatchCreateSessions * for best practices on session cache management. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   BatchCreateSessionsRequest request =
    +   *       BatchCreateSessionsRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setSessionTemplate(Session.newBuilder().build())
    +   *           .setSessionCount(185691686)
    +   *           .build();
    +   *   ApiFuture future =
    +   *       spannerClient.batchCreateSessionsCallable().futureCall(request);
    +   *   // Do something.
    +   *   BatchCreateSessionsResponse response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable batchCreateSessionsCallable() { @@ -353,6 +455,15 @@ public final BatchCreateSessionsResponse batchCreateSessions(BatchCreateSessions * Gets a session. Returns `NOT_FOUND` if the session does not exist. This is mainly useful for * determining whether a session is still alive. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName name = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   Session response = spannerClient.getSession(name);
    +   * }
    +   * }
    + * * @param name Required. The name of the session to retrieve. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -367,6 +478,15 @@ public final Session getSession(SessionName name) { * Gets a session. Returns `NOT_FOUND` if the session does not exist. This is mainly useful for * determining whether a session is still alive. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String name = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   Session response = spannerClient.getSession(name);
    +   * }
    +   * }
    + * * @param name Required. The name of the session to retrieve. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -380,6 +500,19 @@ public final Session getSession(String name) { * Gets a session. Returns `NOT_FOUND` if the session does not exist. This is mainly useful for * determining whether a session is still alive. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   GetSessionRequest request =
    +   *       GetSessionRequest.newBuilder()
    +   *           .setName(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .build();
    +   *   Session response = spannerClient.getSession(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -393,6 +526,19 @@ public final Session getSession(GetSessionRequest request) { * determining whether a session is still alive. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   GetSessionRequest request =
    +   *       GetSessionRequest.newBuilder()
    +   *           .setName(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .build();
    +   *   ApiFuture future = spannerClient.getSessionCallable().futureCall(request);
    +   *   // Do something.
    +   *   Session response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable getSessionCallable() { return stub.getSessionCallable(); @@ -402,6 +548,17 @@ public final UnaryCallable getSessionCallable() { /** * Lists all sessions in a given database. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
    +   *   for (Session element : spannerClient.listSessions(database).iterateAll()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * }
    + * * @param database Required. The database in which to list sessions. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -417,6 +574,17 @@ public final ListSessionsPagedResponse listSessions(DatabaseName database) { /** * Lists all sessions in a given database. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString();
    +   *   for (Session element : spannerClient.listSessions(database).iterateAll()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * }
    + * * @param database Required. The database in which to list sessions. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -429,6 +597,23 @@ public final ListSessionsPagedResponse listSessions(String database) { /** * Lists all sessions in a given database. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ListSessionsRequest request =
    +   *       ListSessionsRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setPageSize(883849137)
    +   *           .setPageToken("pageToken873572522")
    +   *           .setFilter("filter-1274492040")
    +   *           .build();
    +   *   for (Session element : spannerClient.listSessions(request).iterateAll()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -441,6 +626,23 @@ public final ListSessionsPagedResponse listSessions(ListSessionsRequest request) * Lists all sessions in a given database. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ListSessionsRequest request =
    +   *       ListSessionsRequest.newBuilder()
    +   *           .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString())
    +   *           .setPageSize(883849137)
    +   *           .setPageToken("pageToken873572522")
    +   *           .setFilter("filter-1274492040")
    +   *           .build();
    +   *   ApiFuture future = spannerClient.listSessionsPagedCallable().futureCall(request);
    +   *   // Do something.
    +   *   for (Session element : future.get().iterateAll()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * }
    */ public final UnaryCallable listSessionsPagedCallable() { @@ -452,6 +654,23 @@ public final ListSessionsPagedResponse listSessions(ListSessionsRequest request) * Lists all sessions in a given database. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   while (true) {
    +   *     ListSessionsResponse response = spannerClient.listSessionsCallable().call(request);
    +   *     for (Session element : response.getResponsesList()) {
    +   *       // doThingsWith(element);
    +   *     }
    +   *     String nextPageToken = response.getNextPageToken();
    +   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
    +   *       request = request.toBuilder().setPageToken(nextPageToken).build();
    +   *     } else {
    +   *       break;
    +   *     }
    +   *   }
    +   * }
    +   * }
    */ public final UnaryCallable listSessionsCallable() { return stub.listSessionsCallable(); @@ -462,6 +681,15 @@ public final UnaryCallable listSessio * Ends a session, releasing server resources associated with it. This will asynchronously trigger * cancellation of any operations that are running with this session. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName name = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   spannerClient.deleteSession(name);
    +   * }
    +   * }
    + * * @param name Required. The name of the session to delete. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -476,6 +704,15 @@ public final void deleteSession(SessionName name) { * Ends a session, releasing server resources associated with it. This will asynchronously trigger * cancellation of any operations that are running with this session. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String name = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   spannerClient.deleteSession(name);
    +   * }
    +   * }
    + * * @param name Required. The name of the session to delete. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -489,6 +726,19 @@ public final void deleteSession(String name) { * Ends a session, releasing server resources associated with it. This will asynchronously trigger * cancellation of any operations that are running with this session. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   DeleteSessionRequest request =
    +   *       DeleteSessionRequest.newBuilder()
    +   *           .setName(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .build();
    +   *   spannerClient.deleteSession(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -502,6 +752,19 @@ public final void deleteSession(DeleteSessionRequest request) { * cancellation of any operations that are running with this session. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   DeleteSessionRequest request =
    +   *       DeleteSessionRequest.newBuilder()
    +   *           .setName(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .build();
    +   *   ApiFuture future = spannerClient.deleteSessionCallable().futureCall(request);
    +   *   // Do something.
    +   *   future.get();
    +   * }
    +   * }
    */ public final UnaryCallable deleteSessionCallable() { return stub.deleteSessionCallable(); @@ -520,6 +783,27 @@ public final UnaryCallable deleteSessionCallable() *

    Larger result sets can be fetched in streaming fashion by calling * [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] instead. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ExecuteSqlRequest request =
    +   *       ExecuteSqlRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setSql("sql114126")
    +   *           .setParams(Struct.newBuilder().build())
    +   *           .putAllParamTypes(new HashMap())
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .setSeqno(109325920)
    +   *           .setQueryOptions(ExecuteSqlRequest.QueryOptions.newBuilder().build())
    +   *           .build();
    +   *   ResultSet response = spannerClient.executeSql(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -541,6 +825,27 @@ public final ResultSet executeSql(ExecuteSqlRequest request) { * [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] instead. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ExecuteSqlRequest request =
    +   *       ExecuteSqlRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setSql("sql114126")
    +   *           .setParams(Struct.newBuilder().build())
    +   *           .putAllParamTypes(new HashMap())
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .setSeqno(109325920)
    +   *           .setQueryOptions(ExecuteSqlRequest.QueryOptions.newBuilder().build())
    +   *           .build();
    +   *   ApiFuture future = spannerClient.executeSqlCallable().futureCall(request);
    +   *   // Do something.
    +   *   ResultSet response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable executeSqlCallable() { return stub.executeSqlCallable(); @@ -554,6 +859,29 @@ public final UnaryCallable executeSqlCallable() { * MiB, and no column value can exceed 10 MiB. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ExecuteSqlRequest request =
    +   *       ExecuteSqlRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setSql("sql114126")
    +   *           .setParams(Struct.newBuilder().build())
    +   *           .putAllParamTypes(new HashMap())
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .setSeqno(109325920)
    +   *           .setQueryOptions(ExecuteSqlRequest.QueryOptions.newBuilder().build())
    +   *           .build();
    +   *   ServerStream stream =
    +   *       spannerClient.executeStreamingSqlCallable().call(request);
    +   *   for (PartialResultSet response : stream) {
    +   *     // Do something when a response is received.
    +   *   }
    +   * }
    +   * }
    */ public final ServerStreamingCallable executeStreamingSqlCallable() { @@ -573,6 +901,22 @@ public final UnaryCallable executeSqlCallable() { * *

    Execution stops after the first failed statement; the remaining statements are not executed. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ExecuteBatchDmlRequest request =
    +   *       ExecuteBatchDmlRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .addAllStatements(new ArrayList())
    +   *           .setSeqno(109325920)
    +   *           .build();
    +   *   ExecuteBatchDmlResponse response = spannerClient.executeBatchDml(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -594,6 +938,23 @@ public final ExecuteBatchDmlResponse executeBatchDml(ExecuteBatchDmlRequest requ *

    Execution stops after the first failed statement; the remaining statements are not executed. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ExecuteBatchDmlRequest request =
    +   *       ExecuteBatchDmlRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .addAllStatements(new ArrayList())
    +   *           .setSeqno(109325920)
    +   *           .build();
    +   *   ApiFuture future =
    +   *       spannerClient.executeBatchDmlCallable().futureCall(request);
    +   *   // Do something.
    +   *   ExecuteBatchDmlResponse response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable executeBatchDmlCallable() { @@ -614,6 +975,27 @@ public final ExecuteBatchDmlResponse executeBatchDml(ExecuteBatchDmlRequest requ *

    Larger result sets can be yielded in streaming fashion by calling * [StreamingRead][google.spanner.v1.Spanner.StreamingRead] instead. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ReadRequest request =
    +   *       ReadRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setTable("table110115790")
    +   *           .setIndex("index100346066")
    +   *           .addAllColumns(new ArrayList())
    +   *           .setKeySet(KeySet.newBuilder().build())
    +   *           .setLimit(102976443)
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .build();
    +   *   ResultSet response = spannerClient.read(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -636,6 +1018,27 @@ public final ResultSet read(ReadRequest request) { * [StreamingRead][google.spanner.v1.Spanner.StreamingRead] instead. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ReadRequest request =
    +   *       ReadRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setTable("table110115790")
    +   *           .setIndex("index100346066")
    +   *           .addAllColumns(new ArrayList())
    +   *           .setKeySet(KeySet.newBuilder().build())
    +   *           .setLimit(102976443)
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .build();
    +   *   ApiFuture future = spannerClient.readCallable().futureCall(request);
    +   *   // Do something.
    +   *   ResultSet response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable readCallable() { return stub.readCallable(); @@ -649,6 +1052,28 @@ public final UnaryCallable readCallable() { * exceed 10 MiB. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   ReadRequest request =
    +   *       ReadRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setTable("table110115790")
    +   *           .setIndex("index100346066")
    +   *           .addAllColumns(new ArrayList())
    +   *           .setKeySet(KeySet.newBuilder().build())
    +   *           .setLimit(102976443)
    +   *           .setResumeToken(ByteString.EMPTY)
    +   *           .setPartitionToken(ByteString.EMPTY)
    +   *           .build();
    +   *   ServerStream stream = spannerClient.streamingReadCallable().call(request);
    +   *   for (PartialResultSet response : stream) {
    +   *     // Do something when a response is received.
    +   *   }
    +   * }
    +   * }
    */ public final ServerStreamingCallable streamingReadCallable() { return stub.streamingReadCallable(); @@ -660,6 +1085,16 @@ public final ServerStreamingCallable streamingRea * [Read][google.spanner.v1.Spanner.Read], [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] and * [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a side-effect. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   TransactionOptions options = TransactionOptions.newBuilder().build();
    +   *   Transaction response = spannerClient.beginTransaction(session, options);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction runs. * @param options Required. Options for the new transaction. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -679,6 +1114,17 @@ public final Transaction beginTransaction(SessionName session, TransactionOption * [Read][google.spanner.v1.Spanner.Read], [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] and * [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a side-effect. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String session =
    +   *       SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   TransactionOptions options = TransactionOptions.newBuilder().build();
    +   *   Transaction response = spannerClient.beginTransaction(session, options);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction runs. * @param options Required. Options for the new transaction. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -695,6 +1141,20 @@ public final Transaction beginTransaction(String session, TransactionOptions opt * [Read][google.spanner.v1.Spanner.Read], [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] and * [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a side-effect. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   BeginTransactionRequest request =
    +   *       BeginTransactionRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setOptions(TransactionOptions.newBuilder().build())
    +   *           .build();
    +   *   Transaction response = spannerClient.beginTransaction(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -709,6 +1169,20 @@ public final Transaction beginTransaction(BeginTransactionRequest request) { * [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a side-effect. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   BeginTransactionRequest request =
    +   *       BeginTransactionRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setOptions(TransactionOptions.newBuilder().build())
    +   *           .build();
    +   *   ApiFuture future = spannerClient.beginTransactionCallable().futureCall(request);
    +   *   // Do something.
    +   *   Transaction response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable beginTransactionCallable() { return stub.beginTransactionCallable(); @@ -724,6 +1198,22 @@ public final UnaryCallable beginTransactio * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   ByteString transactionId = ByteString.EMPTY;
    +   *   List mutations = new ArrayList<>();
    +   *   CommitResponse response = spannerClient.commit(session, transactionId, mutations);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to be committed is running. * @param transactionId Commit a previously-started transaction. * @param mutations The mutations to be executed when this transaction commits. All mutations are @@ -751,6 +1241,22 @@ public final CommitResponse commit( * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   TransactionOptions singleUseTransaction = TransactionOptions.newBuilder().build();
    +   *   List mutations = new ArrayList<>();
    +   *   CommitResponse response = spannerClient.commit(session, singleUseTransaction, mutations);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to be committed is running. * @param singleUseTransaction Execute mutations in a temporary transaction. Note that unlike * commit of a previously-started transaction, commit with a temporary transaction is @@ -784,6 +1290,23 @@ public final CommitResponse commit( * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String session =
    +   *       SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   ByteString transactionId = ByteString.EMPTY;
    +   *   List mutations = new ArrayList<>();
    +   *   CommitResponse response = spannerClient.commit(session, transactionId, mutations);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to be committed is running. * @param transactionId Commit a previously-started transaction. * @param mutations The mutations to be executed when this transaction commits. All mutations are @@ -811,6 +1334,23 @@ public final CommitResponse commit( * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String session =
    +   *       SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   TransactionOptions singleUseTransaction = TransactionOptions.newBuilder().build();
    +   *   List mutations = new ArrayList<>();
    +   *   CommitResponse response = spannerClient.commit(session, singleUseTransaction, mutations);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to be committed is running. * @param singleUseTransaction Execute mutations in a temporary transaction. Note that unlike * commit of a previously-started transaction, commit with a temporary transaction is @@ -844,6 +1384,26 @@ public final CommitResponse commit( * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   CommitRequest request =
    +   *       CommitRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .addAllMutations(new ArrayList())
    +   *           .setReturnCommitStats(true)
    +   *           .build();
    +   *   CommitResponse response = spannerClient.commit(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -861,7 +1421,27 @@ public final CommitResponse commit(CommitRequest request) { * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt the transaction from the * beginning, re-using the same session. * + *

    On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, for example, if + * the client job experiences a 1+ hour networking failure. At that point, Cloud Spanner has lost + * track of the transaction outcome and we recommend that you perform another read from the + * database to see the state of things as they are now. + * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   CommitRequest request =
    +   *       CommitRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .addAllMutations(new ArrayList())
    +   *           .setReturnCommitStats(true)
    +   *           .build();
    +   *   ApiFuture future = spannerClient.commitCallable().futureCall(request);
    +   *   // Do something.
    +   *   CommitResponse response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable commitCallable() { return stub.commitCallable(); @@ -877,6 +1457,16 @@ public final UnaryCallable commitCallable() { *

    `Rollback` returns `OK` if it successfully aborts the transaction, the transaction was * already aborted, or the transaction is not found. `Rollback` never returns `ABORTED`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]");
    +   *   ByteString transactionId = ByteString.EMPTY;
    +   *   spannerClient.rollback(session, transactionId);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to roll back is running. * @param transactionId Required. The transaction to roll back. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -900,6 +1490,17 @@ public final void rollback(SessionName session, ByteString transactionId) { *

    `Rollback` returns `OK` if it successfully aborts the transaction, the transaction was * already aborted, or the transaction is not found. `Rollback` never returns `ABORTED`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   String session =
    +   *       SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString();
    +   *   ByteString transactionId = ByteString.EMPTY;
    +   *   spannerClient.rollback(session, transactionId);
    +   * }
    +   * }
    + * * @param session Required. The session in which the transaction to roll back is running. * @param transactionId Required. The transaction to roll back. * @throws com.google.api.gax.rpc.ApiException if the remote call fails @@ -920,6 +1521,20 @@ public final void rollback(String session, ByteString transactionId) { *

    `Rollback` returns `OK` if it successfully aborts the transaction, the transaction was * already aborted, or the transaction is not found. `Rollback` never returns `ABORTED`. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   RollbackRequest request =
    +   *       RollbackRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransactionId(ByteString.EMPTY)
    +   *           .build();
    +   *   spannerClient.rollback(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -938,6 +1553,20 @@ public final void rollback(RollbackRequest request) { * already aborted, or the transaction is not found. `Rollback` never returns `ABORTED`. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   RollbackRequest request =
    +   *       RollbackRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransactionId(ByteString.EMPTY)
    +   *           .build();
    +   *   ApiFuture future = spannerClient.rollbackCallable().futureCall(request);
    +   *   // Do something.
    +   *   future.get();
    +   * }
    +   * }
    */ public final UnaryCallable rollbackCallable() { return stub.rollbackCallable(); @@ -956,6 +1585,24 @@ public final UnaryCallable rollbackCallable() { * too long, begins a new transaction, or becomes too old. When any of these happen, it is not * possible to resume the query, and the whole operation must be restarted from the beginning. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   PartitionQueryRequest request =
    +   *       PartitionQueryRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setSql("sql114126")
    +   *           .setParams(Struct.newBuilder().build())
    +   *           .putAllParamTypes(new HashMap())
    +   *           .setPartitionOptions(PartitionOptions.newBuilder().build())
    +   *           .build();
    +   *   PartitionResponse response = spannerClient.partitionQuery(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -977,6 +1624,25 @@ public final PartitionResponse partitionQuery(PartitionQueryRequest request) { * possible to resume the query, and the whole operation must be restarted from the beginning. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   PartitionQueryRequest request =
    +   *       PartitionQueryRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setSql("sql114126")
    +   *           .setParams(Struct.newBuilder().build())
    +   *           .putAllParamTypes(new HashMap())
    +   *           .setPartitionOptions(PartitionOptions.newBuilder().build())
    +   *           .build();
    +   *   ApiFuture future =
    +   *       spannerClient.partitionQueryCallable().futureCall(request);
    +   *   // Do something.
    +   *   PartitionResponse response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable partitionQueryCallable() { return stub.partitionQueryCallable(); @@ -996,6 +1662,25 @@ public final UnaryCallable partitionQu * too long, begins a new transaction, or becomes too old. When any of these happen, it is not * possible to resume the read, and the whole operation must be restarted from the beginning. * + *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   PartitionReadRequest request =
    +   *       PartitionReadRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setTable("table110115790")
    +   *           .setIndex("index100346066")
    +   *           .addAllColumns(new ArrayList())
    +   *           .setKeySet(KeySet.newBuilder().build())
    +   *           .setPartitionOptions(PartitionOptions.newBuilder().build())
    +   *           .build();
    +   *   PartitionResponse response = spannerClient.partitionRead(request);
    +   * }
    +   * }
    + * * @param request The request object containing all of the parameters for the API call. * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ @@ -1018,6 +1703,26 @@ public final PartitionResponse partitionRead(PartitionReadRequest request) { * possible to resume the read, and the whole operation must be restarted from the beginning. * *

    Sample code: + * + *

    {@code
    +   * try (SpannerClient spannerClient = SpannerClient.create()) {
    +   *   PartitionReadRequest request =
    +   *       PartitionReadRequest.newBuilder()
    +   *           .setSession(
    +   *               SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
    +   *           .setTransaction(TransactionSelector.newBuilder().build())
    +   *           .setTable("table110115790")
    +   *           .setIndex("index100346066")
    +   *           .addAllColumns(new ArrayList())
    +   *           .setKeySet(KeySet.newBuilder().build())
    +   *           .setPartitionOptions(PartitionOptions.newBuilder().build())
    +   *           .build();
    +   *   ApiFuture future =
    +   *       spannerClient.partitionReadCallable().futureCall(request);
    +   *   // Do something.
    +   *   PartitionResponse response = future.get();
    +   * }
    +   * }
    */ public final UnaryCallable partitionReadCallable() { return stub.partitionReadCallable(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/package-info.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/package-info.java index 7e442cc8b47..0f340c9469a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/package-info.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/v1/package-info.java @@ -25,6 +25,13 @@ * in Cloud Spanner databases. * *

    Sample for SpannerClient: + * + *

    {@code
    + * try (SpannerClient spannerClient = SpannerClient.create()) {
    + *   DatabaseName database = DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]");
    + *   Session response = spannerClient.createSession(database);
    + * }
    + * }
    */ @Generated("by gapic-generator-java") package com.google.cloud.spanner.v1; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplStressTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplStressTest.java index 71447d46e7e..0c1fb017046 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplStressTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncResultSetImplStressTest.java @@ -55,7 +55,7 @@ @RunWith(Parameterized.class) public class AsyncResultSetImplStressTest { - private static final int TEST_RUNS = 1000; + private static final int TEST_RUNS = 25; /** Timeout is applied to each test case individually. */ @Rule public Timeout timeout = new Timeout(120, TimeUnit.SECONDS); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerImplTest.java new file mode 100644 index 00000000000..fab29d29005 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerImplTest.java @@ -0,0 +1,156 @@ +/* + * 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.google.cloud.spanner; + +import static com.google.cloud.spanner.SpannerApiFutures.get; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.cloud.spanner.AsyncRunner.AsyncWork; +import com.google.cloud.spanner.TransactionRunner.TransactionCallable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AsyncRunnerImplTest { + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @AfterClass + public static void teardown() { + executor.shutdown(); + } + + @SuppressWarnings("unchecked") + @Test + public void testAsyncRunReturnsResultAndCommitResponse() { + final Object expectedResult = new Object(); + final CommitResponse expectedCommitResponse = mock(CommitResponse.class); + + TransactionRunnerImpl delegate = mock(TransactionRunnerImpl.class); + when(delegate.run(any(TransactionCallable.class))).thenReturn(expectedResult); + when(delegate.getCommitResponse()).thenReturn(expectedCommitResponse); + + AsyncRunnerImpl runner = new AsyncRunnerImpl(delegate); + ApiFuture result = + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + return ApiFutures.immediateFuture(expectedResult); + } + }, + executor); + + assertSame(expectedResult, get(result)); + assertSame(expectedCommitResponse, get(runner.getCommitResponse())); + assertEquals( + get(runner.getCommitResponse()).getCommitTimestamp(), get(runner.getCommitTimestamp())); + } + + @Test + public void testGetCommitTimestampReturnsErrorBeforeRun() { + TransactionRunnerImpl delegate = mock(TransactionRunnerImpl.class); + AsyncRunnerImpl runner = new AsyncRunnerImpl(delegate); + try { + runner.getCommitTimestamp(); + fail("missing expected exception"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("runAsync() has not yet been called")); + } + } + + @Test + public void testGetCommitResponseReturnsErrorBeforeRun() { + TransactionRunnerImpl delegate = mock(TransactionRunnerImpl.class); + AsyncRunnerImpl runner = new AsyncRunnerImpl(delegate); + try { + runner.getCommitResponse(); + fail("missing expected exception"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("runAsync() has not yet been called")); + } + } + + @Test + public void testGetCommitResponseReturnsErrorIfRunFails() { + final SpannerException expectedException = + SpannerExceptionFactory.newSpannerException(ErrorCode.ALREADY_EXISTS, "Row already exists"); + + TransactionRunnerImpl delegate = mock(TransactionRunnerImpl.class); + when(delegate.getCommitResponse()).thenThrow(expectedException); + + AsyncRunnerImpl runner = new AsyncRunnerImpl(delegate); + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + return ApiFutures.immediateFailedFuture(expectedException); + } + }, + executor); + + try { + get(runner.getCommitResponse()); + fail("missing expected exception"); + } catch (SpannerException e) { + assertSame(expectedException, e); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testRunAyncFailsIfCalledMultipleTimes() { + final Object result = new Object(); + TransactionRunnerImpl delegate = mock(TransactionRunnerImpl.class); + when(delegate.run(any(TransactionCallable.class))).thenReturn(result); + + AsyncRunnerImpl runner = new AsyncRunnerImpl(delegate); + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + return ApiFutures.immediateFuture(result); + } + }, + executor); + + try { + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + return ApiFutures.immediateFuture(null); + } + }, + executor); + fail("missing expected exception"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("runAsync() can only be called once")); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java index 2af185ae144..0c67871e899 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java @@ -18,6 +18,7 @@ import static com.google.cloud.spanner.MockSpannerTestUtil.*; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.core.ApiFunction; @@ -53,6 +54,29 @@ @RunWith(JUnit4.class) public class AsyncRunnerTest extends AbstractAsyncTransactionTest { + + @Test + public void testAsyncRunner_doesNotReturnCommitTimestampBeforeCommit() { + AsyncRunner runner = client().runAsync(); + try { + runner.getCommitTimestamp(); + fail("missing expected exception"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("runAsync() has not yet been called")); + } + } + + @Test + public void testAsyncRunner_doesNotReturnCommitResponseBeforeCommit() { + AsyncRunner runner = client().runAsync(); + try { + runner.getCommitResponse(); + fail("missing expected exception"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("runAsync() has not yet been called")); + } + } + @Test public void asyncRunnerUpdate() throws Exception { AsyncRunner runner = client().runAsync(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java new file mode 100644 index 00000000000..c60363e7932 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java @@ -0,0 +1,53 @@ +/* + * 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.google.cloud.spanner; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.core.ApiFutures; +import com.google.cloud.Timestamp; +import io.opencensus.trace.Span; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class AsyncTransactionManagerImplTest { + + @Mock private SessionImpl session; + @Mock TransactionRunnerImpl.TransactionContextImpl transaction; + + @Test + public void testCommitReturnsCommitStats() { + try (AsyncTransactionManagerImpl manager = + new AsyncTransactionManagerImpl(session, mock(Span.class), Options.commitStats())) { + when(session.newTransaction(Options.fromTransactionOptions(Options.commitStats()))) + .thenReturn(transaction); + when(transaction.ensureTxnAsync()).thenReturn(ApiFutures.immediateFuture(null)); + Timestamp commitTimestamp = Timestamp.ofTimeMicroseconds(1); + CommitResponse response = mock(CommitResponse.class); + when(response.getCommitTimestamp()).thenReturn(commitTimestamp); + when(transaction.commitAsync()).thenReturn(ApiFutures.immediateFuture(response)); + manager.beginAsync(); + manager.commitAsync(); + verify(transaction).commitAsync(); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java index aed844ed9ad..748d6f76405 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java @@ -23,6 +23,8 @@ import static com.google.cloud.spanner.MockSpannerTestUtil.UPDATE_COUNT; import static com.google.cloud.spanner.MockSpannerTestUtil.UPDATE_STATEMENT; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import com.google.api.core.ApiFuture; @@ -213,6 +215,32 @@ public boolean apply(AbstractMessage input) { 0L); } + @Test + public void testAsyncTransactionManager_returnsCommitStats() throws Exception { + try (AsyncTransactionManager manager = + client().transactionManagerAsync(Options.commitStats())) { + TransactionContextFuture transaction = manager.beginAsync(); + while (true) { + try { + CommitTimestampFuture commitTimestamp = + transaction + .then( + AsyncTransactionManagerHelper.buffer( + Mutation.delete("FOO", Key.of("foo"))), + executor) + .commitAsync(); + assertNotNull(commitTimestamp.get()); + assertNotNull(manager.getCommitResponse().get()); + assertNotNull(manager.getCommitResponse().get().getCommitStats()); + assertEquals(1L, manager.getCommitResponse().get().getCommitStats().getMutationCount()); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetryAsync(); + } + } + } + } + @Test public void asyncTransactionManagerUpdate() throws Exception { final SettableApiFuture updateCount = SettableApiFuture.create(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackendExhaustedTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackendExhaustedTest.java index cb1244f8e3e..7e4e41facc0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackendExhaustedTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackendExhaustedTest.java @@ -136,7 +136,6 @@ public void setUp() throws Exception { SessionPoolOptions.newBuilder() .setMinSessions(executor.getCorePoolSize()) .setMaxSessions(executor.getCorePoolSize() * 3) - .setWriteSessionsFraction(0.0f) .build()) .build(); executorFactory.release(executor); @@ -159,7 +158,7 @@ public void tearDown() { // This test case force-closes the Spanner instance as it would otherwise wait // forever on the BatchCreateSessions requests that are 'stuck'. try { - ((SpannerImpl) spanner).close(100L, TimeUnit.MILLISECONDS); + ((SpannerImpl) spanner).close(10L, TimeUnit.MILLISECONDS); } catch (SpannerException e) { // ignore any errors during close as they are expected. } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java index 3bdc6738382..80b99f679b4 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java @@ -46,6 +46,7 @@ public class BackupTest { "projects/test-project/instances/test-instance/backups/backup-1"; private static final String DB = "projects/test-project/instances/test-instance/databases/db-1"; private static final Timestamp EXP_TIME = Timestamp.ofTimeSecondsAndNanos(1000L, 1000); + private static final Timestamp VERSION_TIME = Timestamp.ofTimeSecondsAndNanos(2000L, 2000); @Mock DatabaseAdminClient dbClient; @@ -65,11 +66,13 @@ public Builder answer(InvocationOnMock invocation) { @Test public void build() { Timestamp expireTime = Timestamp.now(); + Timestamp versionTime = Timestamp.ofTimeMicroseconds(10L); Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) .setExpireTime(expireTime) + .setVersionTime(versionTime) .setSize(100L) .setState(State.CREATING) .build(); @@ -77,6 +80,7 @@ public void build() { assertThat(copy.getId()).isEqualTo(backup.getId()); assertThat(copy.getDatabase()).isEqualTo(backup.getDatabase()); assertThat(copy.getExpireTime()).isEqualTo(backup.getExpireTime()); + assertThat(copy.getVersionTime()).isEqualTo(backup.getVersionTime()); assertThat(copy.getSize()).isEqualTo(backup.getSize()); assertThat(copy.getState()).isEqualTo(backup.getState()); } @@ -84,14 +88,16 @@ public void build() { @Test public void create() { Timestamp expireTime = Timestamp.now(); + Timestamp versionTime = Timestamp.ofTimeMicroseconds(10L); Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) .setExpireTime(expireTime) + .setVersionTime(versionTime) .build(); backup.create(); - verify(dbClient).createBackup("instance-id", "backup-id", "src-database", expireTime); + verify(dbClient).createBackup(backup); } @Test @@ -125,6 +131,19 @@ public void createWithoutExpireTime() { } } + @Test + public void createWithoutVersionTimeShouldSucceed() { + final Timestamp expireTime = Timestamp.now(); + Backup backup = + dbClient + .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) + .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) + .setExpireTime(expireTime) + .build(); + backup.create(); + verify(dbClient).createBackup(backup); + } + @Test public void exists() { when(dbClient.getBackup("test-instance", "test-backup")) @@ -298,6 +317,7 @@ public void fromProto() { assertThat(backup.getId().getName()).isEqualTo(NAME); assertThat(backup.getState()).isEqualTo(BackupInfo.State.CREATING); assertThat(backup.getExpireTime()).isEqualTo(EXP_TIME); + assertThat(backup.getVersionTime()).isEqualTo(VERSION_TIME); } private Backup createBackup() { @@ -307,6 +327,8 @@ private Backup createBackup() { .setDatabase(DB) .setExpireTime( com.google.protobuf.Timestamp.newBuilder().setSeconds(1000L).setNanos(1000).build()) + .setVersionTime( + com.google.protobuf.Timestamp.newBuilder().setSeconds(2000L).setNanos(2000).build()) .setState(com.google.spanner.admin.database.v1.Backup.State.CREATING) .build(); return Backup.fromProto(proto, dbClient); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BaseSessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BaseSessionPoolTest.java index 6fd6a4383e3..3db2bdb1c4f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BaseSessionPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BaseSessionPoolTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.when; import com.google.api.core.ApiFutures; -import com.google.cloud.Timestamp; import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory; import com.google.cloud.spanner.SessionPool.Clock; import com.google.protobuf.Empty; @@ -67,9 +66,9 @@ SessionImpl mockSession() { "projects/dummy/instances/dummy/database/dummy/sessions/session" + sessionIndex); when(session.asyncClose()).thenReturn(ApiFutures.immediateFuture(Empty.getDefaultInstance())); when(session.writeWithOptions(any(Iterable.class))) - .thenReturn(new CommitResponse(Timestamp.now())); + .thenReturn(new CommitResponse(com.google.spanner.v1.CommitResponse.getDefaultInstance())); when(session.writeAtLeastOnceWithOptions(any(Iterable.class))) - .thenReturn(new CommitResponse(Timestamp.now())); + .thenReturn(new CommitResponse(com.google.spanner.v1.CommitResponse.getDefaultInstance())); sessionIndex++; return session; } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/CommitResponseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/CommitResponseTest.java new file mode 100644 index 00000000000..7fb4c559d4d --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/CommitResponseTest.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.google.cloud.spanner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import com.google.cloud.Timestamp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommitResponseTest { + + @Test + public void testConstructWithTimestamp() { + Timestamp timestamp = Timestamp.ofTimeSecondsAndNanos(100L, 100); + CommitResponse response = new CommitResponse(timestamp); + assertEquals(timestamp, response.getCommitTimestamp()); + } + + @Test + public void testFromProto() { + long mutationCount = 5L; + com.google.protobuf.Timestamp timestamp = + com.google.protobuf.Timestamp.newBuilder().setSeconds(123L).setNanos(456).build(); + com.google.spanner.v1.CommitResponse proto = + com.google.spanner.v1.CommitResponse.newBuilder() + .setCommitStats( + com.google.spanner.v1.CommitResponse.CommitStats.newBuilder() + .setMutationCount(mutationCount) + .build()) + .setCommitTimestamp(timestamp) + .build(); + + CommitResponse response = new CommitResponse(proto); + + assertEquals(Timestamp.ofTimeSecondsAndNanos(123L, 456), response.getCommitTimestamp()); + assertEquals(mutationCount, response.getCommitStats().getMutationCount()); + } + + @Test + public void testEqualsAndHashCode() { + com.google.spanner.v1.CommitResponse proto1 = + com.google.spanner.v1.CommitResponse.newBuilder() + .setCommitTimestamp(com.google.protobuf.Timestamp.newBuilder().setSeconds(1L).build()) + .build(); + com.google.spanner.v1.CommitResponse proto2 = + com.google.spanner.v1.CommitResponse.newBuilder() + .setCommitTimestamp(com.google.protobuf.Timestamp.newBuilder().setSeconds(2L).build()) + .build(); + com.google.spanner.v1.CommitResponse proto3 = + com.google.spanner.v1.CommitResponse.newBuilder() + .setCommitTimestamp(com.google.protobuf.Timestamp.newBuilder().setSeconds(1L).build()) + .build(); + + CommitResponse response1 = new CommitResponse(proto1); + CommitResponse response2 = new CommitResponse(proto2); + CommitResponse response3 = new CommitResponse(proto3); + + assertEquals(response3, response1); + assertNotEquals(response2, response1); + assertNotEquals(response3, response2); + assertNotEquals(response1, null); + assertNotEquals(response1, new Object()); + + assertEquals(response3.hashCode(), response1.hashCode()); + assertNotEquals(response2.hashCode(), response1.hashCode()); + assertNotEquals(response3.hashCode(), response2.hashCode()); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index b2676473cf1..fb617797aa0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -48,6 +48,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Before; @@ -69,6 +70,8 @@ public class DatabaseAdminClientImplTest { private static final String BK_ID = "my-bk"; private static final String BK_NAME = "projects/my-project/instances/my-instance/backups/my-bk"; private static final String BK_NAME2 = "projects/my-project/instances/my-instance/backups/my-bk2"; + private static final Timestamp EARLIEST_VERSION_TIME = Timestamp.now(); + private static final String VERSION_RETENTION_PERIOD = "7d"; @Mock SpannerRpc rpc; DatabaseAdminClientImpl client; @@ -80,7 +83,12 @@ public void setUp() { } private Database getDatabaseProto() { - return Database.newBuilder().setName(DB_NAME).setState(Database.State.READY).build(); + return Database.newBuilder() + .setName(DB_NAME) + .setState(Database.State.READY) + .setEarliestVersionTime(EARLIEST_VERSION_TIME.toProto()) + .setVersionRetentionPeriod(VERSION_RETENTION_PERIOD) + .build(); } private Database getAnotherDatabaseProto() { @@ -116,6 +124,8 @@ public void getDatabase() { com.google.cloud.spanner.Database db = client.getDatabase(INSTANCE_ID, DB_ID); assertThat(db.getId().getName()).isEqualTo(DB_NAME); assertThat(db.getState()).isEqualTo(DatabaseInfo.State.READY); + assertThat(db.getEarliestVersionTime()).isEqualTo(EARLIEST_VERSION_TIME); + assertThat(db.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); } @Test @@ -292,7 +302,7 @@ public void testDatabaseIAMPermissions() { } @Test - public void createBackup() throws Exception { + public void createBackupWithParams() throws Exception { OperationFuture rawOperationFuture = OperationFutureUtil.immediateOperationFuture( "createBackup", getBackupProto(), CreateBackupMetadata.getDefaultInstance()); @@ -308,6 +318,40 @@ public void createBackup() throws Exception { assertThat(op.get().getId().getName()).isEqualTo(BK_NAME); } + @Test + public void createBackupWithBackupObject() throws ExecutionException, InterruptedException { + final OperationFuture rawOperationFuture = + OperationFutureUtil.immediateOperationFuture( + "createBackup", getBackupProto(), CreateBackupMetadata.getDefaultInstance()); + final Timestamp expireTime = + Timestamp.ofTimeMicroseconds( + TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()) + + TimeUnit.HOURS.toMicros(28)); + final Timestamp versionTime = + Timestamp.ofTimeMicroseconds( + TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()) - TimeUnit.DAYS.toMicros(2)); + final Backup expectedCallBackup = + Backup.newBuilder() + .setDatabase(DB_NAME) + .setExpireTime(expireTime.toProto()) + .setVersionTime(versionTime.toProto()) + .build(); + final com.google.cloud.spanner.Backup requestBackup = + client + .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BK_ID)) + .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + when(rpc.createBackup(INSTANCE_NAME, BK_ID, expectedCallBackup)).thenReturn(rawOperationFuture); + + final OperationFuture op = + client.createBackup(requestBackup); + assertThat(op.isDone()).isTrue(); + assertThat(op.get().getId().getName()).isEqualTo(BK_NAME); + } + @Test public void deleteBackup() { client.deleteBackup(INSTANCE_ID, BK_ID); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java index 8b30df912d3..95641ef3fff 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -224,7 +225,7 @@ public void tearDown() { public void dbAdminCreateBackup() throws InterruptedException, ExecutionException { final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( @@ -240,7 +241,8 @@ public void backupCreate() throws InterruptedException, ExecutionException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) + .setVersionTime(sevenDaysAgo()) .build(); OperationFuture op = backup.create(); backup = op.get(); @@ -251,6 +253,25 @@ public void backupCreate() throws InterruptedException, ExecutionException { assertThat(client.getBackup(INSTANCE_ID, backupId)).isEqualTo(backup); } + @Test + public void databaseAdminBackupCreate() throws ExecutionException, InterruptedException { + final String backupId = "other-backup-id"; + Backup backup = + client + .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) + .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) + .setExpireTime(afterDays(7)) + .setVersionTime(sevenDaysAgo()) + .build(); + final OperationFuture op = client.createBackup(backup); + backup = op.get(); + assertThat(backup.getId().getName()) + .isEqualTo( + String.format( + "projects/%s/instances/%s/backups/%s", PROJECT_ID, INSTANCE_ID, backupId)); + assertThat(client.getBackup(INSTANCE_ID, backupId)).isEqualTo(backup); + } + @Test public void backupCreateCancel() { final String backupId = "other-backup-id"; @@ -299,7 +320,7 @@ public void databaseBackup() throws InterruptedException, ExecutionException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()) .get(); assertThat(backup.getId().getName()) @@ -312,7 +333,7 @@ public void databaseBackup() throws InterruptedException, ExecutionException { @Test public void dbAdminCreateBackupAlreadyExists() throws InterruptedException { OperationFuture op = - client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, afterDays(7)); try { op.get(); fail("missing expected exception"); @@ -329,7 +350,7 @@ public void backupCreateAlreadyExists() throws InterruptedException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build(); try { backup.create().get(); @@ -348,7 +369,7 @@ public void databaseBackupAlreadyExists() throws InterruptedException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()); try { op.get(); @@ -364,7 +385,7 @@ public void databaseBackupAlreadyExists() throws InterruptedException { public void dbAdminCreateBackupDbNotFound() throws InterruptedException { final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, "does-not-exist", after7Days()); + client.createBackup(INSTANCE_ID, backupId, "does-not-exist", afterDays(7)); try { op.get(); fail("missing expected exception"); @@ -381,7 +402,7 @@ public void backupCreateDbNotFound() throws InterruptedException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, "does-not-exist")) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build(); try { backup.create().get(); @@ -402,7 +423,7 @@ public void databaseBackupDbNotFound() throws InterruptedException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()); try { op.get(); @@ -499,7 +520,7 @@ public void dbClientListBackups() throws SpannerException, InterruptedException, ExecutionException { Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup, backup2); backup2.delete(); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup); @@ -515,7 +536,7 @@ public void instanceListBackups() .build(); Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); assertThat(instance.listBackups().iterateAll()).containsExactly(backup, backup2); backup2.delete(); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); @@ -532,7 +553,7 @@ public void instanceListBackupsWithFilter() Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); // All backups. assertThat(instance.listBackups().iterateAll()).containsExactly(backup, backup2); @@ -549,7 +570,7 @@ public void instanceListBackupsWithFilter() .containsExactly(backup, backup2); // All backups that expire before a certain time. - String ts = after14Days().toString(); + String ts = afterDays(14).toString(); filter = String.format("expire_time < \"%s\"", ts); mockDatabaseAdmin.addFilterMatches(filter, backup.getId().getName(), backup2.getId().getName()); assertThat(instance.listBackups(Options.filter(filter)).iterateAll()) @@ -701,7 +722,7 @@ public void databaseListDatabaseOperations() public void dbClientListBackupOperations() throws SpannerException, InterruptedException, ExecutionException { assertThat(client.listBackupOperations(INSTANCE_ID).iterateAll()).hasSize(1); - client.createBackup(INSTANCE_ID, "other-backup", DB_ID, after7Days()).get(); + client.createBackup(INSTANCE_ID, "other-backup", DB_ID, afterDays(7)).get(); assertThat(client.listBackupOperations(INSTANCE_ID).iterateAll()).hasSize(2); // Restore a backup. This creates 2 DATABASE operations: One to restore the database and // one to optimize it. @@ -723,7 +744,7 @@ public void instanceListBackupOperations() .backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, "other-backup")) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()) .get(); assertThat(instance.listBackupOperations().iterateAll()).hasSize(2); @@ -768,7 +789,7 @@ public void backupListBackupOperations() Backup backup = client.newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)).build(); mockDatabaseAdmin.addFilterMatches("name:backups/" + BCK_ID, createBackupOperation.getName()); assertThat(backup.listBackupOperations().iterateAll()).hasSize(1); - client.createBackup(INSTANCE_ID, "other-backup", DB_ID, after7Days()).get(); + client.createBackup(INSTANCE_ID, "other-backup", DB_ID, afterDays(7)).get(); assertThat(backup.listBackupOperations().iterateAll()).hasSize(1); } @@ -791,16 +812,10 @@ public void testDatabaseIAMPermissions() { assertThat(permissions).containsExactly("spanner.databases.select"); } - private Timestamp after7Days() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(7, TimeUnit.DAYS)); - } - - private Timestamp after14Days() { + private Timestamp sevenDaysAgo() { return Timestamp.ofTimeMicroseconds( TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(14, TimeUnit.DAYS)); + - TimeUnit.MICROSECONDS.convert(7, TimeUnit.DAYS)); } private void createTestDatabase() { @@ -814,7 +829,7 @@ private void createTestDatabase() { private void createTestBackup() { try { - createBackupOperation = client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, after7Days()); + createBackupOperation = client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, afterDays(7)); createBackupOperation.get(); } catch (InterruptedException | ExecutionException e) { throw SpannerExceptionFactory.newSpannerException(e); @@ -839,7 +854,7 @@ public void retryCreateBackupSlowResponse() throws Exception { SimulatedExecutionTime.ofException(Status.DEADLINE_EXCEEDED.asRuntimeException())); final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( @@ -857,7 +872,7 @@ public void retryCreateBackupSlowStartup() throws Exception { SimulatedExecutionTime.ofException(Status.DEADLINE_EXCEEDED.asRuntimeException())); final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( 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 8ed55e0b9e2..42a158d755b 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 @@ -17,7 +17,11 @@ package com.google.cloud.spanner; import static com.google.cloud.spanner.MockSpannerTestUtil.SELECT1; +import static com.google.cloud.spanner.SpannerApiFutures.get; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -28,6 +32,7 @@ import com.google.api.gax.grpc.testing.LocalChannelProvider; import com.google.api.gax.retrying.RetrySettings; import com.google.cloud.NoCredentials; +import com.google.cloud.Timestamp; import com.google.cloud.spanner.AbstractResultSet.GrpcStreamIterator; import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; import com.google.cloud.spanner.AsyncResultSet.ReadyCallback; @@ -155,21 +160,53 @@ public void tearDown() { } @Test - public void write() { + public void testWrite() { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); - client.write( - Arrays.asList( - Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build())); + Timestamp timestamp = + client.write( + Arrays.asList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build())); + assertNotNull(timestamp); } @Test - public void writeAtLeastOnce() { + public void testWriteWithCommitStats() { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); - client.writeAtLeastOnce( - Arrays.asList( - Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build())); + CommitResponse response = + client.writeWithOptions( + Arrays.asList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build()), + Options.commitStats()); + assertNotNull(response); + assertNotNull(response.getCommitTimestamp()); + assertNotNull(response.getCommitStats()); + } + + @Test + public void testWriteAtLeastOnce() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); + Timestamp timestamp = + client.writeAtLeastOnce( + Arrays.asList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build())); + assertNotNull(timestamp); + } + + @Test + public void testWriteAtLeastOnceWithCommitStats() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); + CommitResponse response = + client.writeAtLeastOnceWithOptions( + Arrays.asList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build()), + Options.commitStats()); + assertNotNull(response); + assertNotNull(response.getCommitTimestamp()); + assertNotNull(response.getCommitStats()); } @Test @@ -426,7 +463,7 @@ public void readOnlyTransactionBoundIsNonBlocking() { } @Test - public void readWriteTransaction() { + public void testReadWriteTransaction() { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); TransactionRunner runner = client.readWriteTransaction(); @@ -438,6 +475,25 @@ public Void run(TransactionContext transaction) throws Exception { return null; } }); + assertNotNull(runner.getCommitTimestamp()); + } + + @Test + public void testReadWriteTransaction_returnsCommitStats() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); + TransactionRunner runner = client.readWriteTransaction(Options.commitStats()); + runner.run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.buffer(Mutation.delete("FOO", Key.of("foo"))); + return null; + } + }); + assertNotNull(runner.getCommitResponse()); + assertNotNull(runner.getCommitResponse().getCommitStats()); + assertEquals(1L, runner.getCommitResponse().getCommitStats().getMutationCount()); } @Test @@ -461,12 +517,12 @@ public Void run(TransactionContext transaction) throws Exception { } @Test - public void runAsync() throws Exception { + public void testRunAsync() throws Exception { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); ExecutorService executor = Executors.newSingleThreadExecutor(); AsyncRunner runner = client.runAsync(); - ApiFuture fut = + ApiFuture result = runner.runAsync( new AsyncWork() { @Override @@ -475,7 +531,31 @@ public ApiFuture doWorkAsync(TransactionContext txn) { } }, executor); - assertThat(fut.get()).isEqualTo(UPDATE_COUNT); + assertEquals(UPDATE_COUNT, result.get().longValue()); + assertNotNull(runner.getCommitTimestamp().get()); + executor.shutdown(); + } + + @Test + public void testRunAsync_returnsCommitStats() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); + ExecutorService executor = Executors.newSingleThreadExecutor(); + AsyncRunner runner = client.runAsync(Options.commitStats()); + ApiFuture result = + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + txn.buffer(Mutation.delete("FOO", Key.of("foo"))); + return ApiFutures.immediateFuture(null); + } + }, + executor); + assertNull(get(result)); + assertNotNull(get(runner.getCommitResponse())); + assertNotNull(get(runner.getCommitResponse()).getCommitStats()); + assertEquals(1L, get(runner.getCommitResponse()).getCommitStats().getMutationCount()); executor.shutdown(); } @@ -528,19 +608,40 @@ public ApiFuture doWorkAsync(TransactionContext txn) { } @Test - public void transactionManager() throws Exception { + public void testTransactionManager() throws Exception { DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); - try (TransactionManager txManager = client.transactionManager()) { + try (TransactionManager manager = client.transactionManager()) { while (true) { - TransactionContext tx = txManager.begin(); + TransactionContext transaction = manager.begin(); try { - tx.executeUpdate(UPDATE_STATEMENT); - txManager.commit(); + transaction.executeUpdate(UPDATE_STATEMENT); + manager.commit(); + assertNotNull(manager.getCommitTimestamp()); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis() / 1000); - tx = txManager.resetForRetry(); + transaction = manager.resetForRetry(); + } + } + } + } + + @Test + public void testTransactionManager_returnsCommitStats() throws InterruptedException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE)); + try (TransactionManager manager = client.transactionManager(Options.commitStats())) { + while (true) { + TransactionContext transaction = manager.begin(); + try { + transaction.buffer(Mutation.delete("FOO", Key.of("foo"))); + manager.commit(); + assertNotNull(manager.getCommitResponse()); + assertNotNull(manager.getCommitResponse().getCommitStats()); + assertEquals(1L, manager.getCommitResponse().getCommitStats().getMutationCount()); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index 95576150071..7c61720b219 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -42,6 +42,9 @@ public class DatabaseTest { private static final String NAME = "projects/test-project/instances/test-instance/databases/database-1"; + private static final Timestamp EARLIEST_VERSION_TIME = Timestamp.now(); + private static final String VERSION_RETENTION_PERIOD = "7d"; + @Mock DatabaseAdminClient dbClient; @Before @@ -61,12 +64,13 @@ public Backup.Builder answer(InvocationOnMock invocation) { public void backup() { Timestamp expireTime = Timestamp.now(); Database db = createDatabase(); - db.backup( + Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "test-instance", "test-backup")) .setExpireTime(expireTime) - .build()); - verify(dbClient).createBackup("test-instance", "test-backup", "database-1", expireTime); + .build(); + db.backup(backup); + verify(dbClient).createBackup(backup.toBuilder().setDatabase(db.getId()).build()); } @Test @@ -82,6 +86,8 @@ public void fromProto() { Database db = createDatabase(); assertThat(db.getId().getName()).isEqualTo(NAME); assertThat(db.getState()).isEqualTo(DatabaseInfo.State.CREATING); + assertThat(db.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(db.getEarliestVersionTime()).isEqualTo(EARLIEST_VERSION_TIME); } @Test @@ -119,6 +125,8 @@ private Database createDatabase() { com.google.spanner.admin.database.v1.Database.newBuilder() .setName(NAME) .setState(com.google.spanner.admin.database.v1.Database.State.CREATING) + .setEarliestVersionTime(EARLIEST_VERSION_TIME.toProto()) + .setVersionRetentionPeriod(VERSION_RETENTION_PERIOD) .build(); return Database.fromProto(proto, dbClient); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ForceCloseSpannerFunction.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ForceCloseSpannerFunction.java new file mode 100644 index 00000000000..a065a20bb52 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ForceCloseSpannerFunction.java @@ -0,0 +1,40 @@ +/* + * 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.google.cloud.spanner; + +import com.google.common.base.Function; +import java.util.concurrent.TimeUnit; + +/** Class for tests that need to be able to force-close a {@link Spanner} instance. */ +public class ForceCloseSpannerFunction implements Function { + private final long timeout; + private final TimeUnit unit; + + public ForceCloseSpannerFunction(long timeout, TimeUnit unit) { + this.timeout = timeout; + this.unit = unit; + } + + public Void apply(Spanner spanner) { + if (spanner instanceof SpannerImpl) { + ((SpannerImpl) spanner).close(timeout, unit); + } else { + spanner.close(); + } + return null; + } +} 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 e86ffd85543..5aa2948b518 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 @@ -61,6 +61,7 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -68,6 +69,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; @@ -75,25 +77,15 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -@RunWith(Parameterized.class) +@RunWith(Enclosed.class) public class InlineBeginTransactionTest { - @Parameter public Executor executor; - - @Parameters(name = "executor = {0}") - public static Collection data() { - return Arrays.asList( - new Object[][] { - {MoreExecutors.directExecutor()}, - {Executors.newSingleThreadExecutor()}, - {Executors.newFixedThreadPool(4)} - }); - } - private static MockSpannerServiceImpl mockSpanner; private static Server server; private static LocalChannelProvider channelProvider; @@ -144,7 +136,7 @@ public static Collection data() { private static final Statement READ_ROW_STATEMENT = Statement.of("SELECT BAR FROM FOO WHERE ID=1"); - private Spanner spanner; + protected Spanner spanner; @BeforeClass public static void startStaticServer() throws IOException { @@ -209,1349 +201,1229 @@ public void tearDown() throws Exception { mockSpanner.clearRequests(); } - @Test - public void testInlinedBeginTx() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - return transaction.executeUpdate(UPDATE_STATEMENT); - } - }); - assertThat(updateCount).isEqualTo(UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + @RunWith(Parameterized.class) + public static class InlineBeginTransactionWithExecutorTest extends InlineBeginTransactionTest { + @Parameter public Executor executor; + + @Parameters(name = "executor = {0}") + public static Collection data() { + return Arrays.asList( + new Object[][] { + {MoreExecutors.directExecutor()}, + {Executors.newSingleThreadExecutor()}, + {Executors.newFixedThreadPool(4)} + }); + } - @Test - public void testInlinedBeginTxAborted() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - final AtomicBoolean firstAttempt = new AtomicBoolean(true); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - long res = transaction.executeUpdate(UPDATE_STATEMENT); - if (firstAttempt.getAndSet(false)) { - mockSpanner.abortTransaction(transaction); + @Test + public void testInlinedBeginAsyncTx() throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + ApiFuture updateCount = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + return txn.executeUpdateAsync(UPDATE_STATEMENT); } - return res; - } - }); - assertThat(updateCount).isEqualTo(UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - // We have started 2 transactions, because the first transaction aborted during the commit. - assertThat(countRequests(CommitRequest.class)).isEqualTo(2); - assertThat(countTransactionsStarted()).isEqualTo(2); - } + }, + executor); + assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginFirstUpdateAborts() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - boolean firstAttempt = true; + @Test + public void testInlinedBeginAsyncTxAborted() throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + final AtomicBoolean firstAttempt = new AtomicBoolean(true); + ApiFuture updateCount = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + ApiFuture res = txn.executeUpdateAsync(UPDATE_STATEMENT); + if (firstAttempt.getAndSet(false)) { + mockSpanner.abortTransaction(txn); + } + return res; + } + }, + executor); + assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + // We have started 2 transactions, because the first transaction aborted. + assertThat(countTransactionsStarted()).isEqualTo(2); + } - @Override - public Long run(TransactionContext transaction) throws Exception { - if (firstAttempt) { - firstAttempt = false; - mockSpanner.putStatementResult( - StatementResult.exception( - UPDATE_STATEMENT, - mockSpanner.createAbortedException( - ByteString.copyFromUtf8("some-tx")))); - } else { - mockSpanner.putStatementResult( - StatementResult.update(UPDATE_STATEMENT, UPDATE_COUNT)); + @Test + public void testInlinedBeginAsyncTxWithQuery() throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); + ApiFuture updateCount = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext txn) { + final SettableApiFuture res = SettableApiFuture.create(); + try (AsyncResultSet rs = txn.executeQueryAsync(SELECT1)) { + rs.setCallback( + executor, + new ReadyCallback() { + @Override + public CallbackResponse cursorReady(AsyncResultSet resultSet) { + switch (resultSet.tryNext()) { + case DONE: + return CallbackResponse.DONE; + case NOT_READY: + return CallbackResponse.CONTINUE; + case OK: + res.set(resultSet.getLong(0)); + default: + throw new IllegalStateException(); + } + } + }); + } + return res; } - return transaction.executeUpdate(UPDATE_STATEMENT); - } - }); - assertThat(updateCount).isEqualTo(UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - } + }, + queryExecutor); + assertThat(updateCount.get()).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + queryExecutor.shutdown(); + } - @Test - public void testInlinedBeginFirstQueryAborts() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - boolean firstAttempt = true; + @Test + public void testInlinedBeginAsyncTxWithBatchDml() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + ApiFuture updateCounts = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext transaction) { + return transaction.batchUpdateAsync( + Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)); + } + }, + executor); + assertThat(updateCounts.get()).asList().containsExactly(UPDATE_COUNT, UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Override - public Long run(TransactionContext transaction) throws Exception { - if (firstAttempt) { - firstAttempt = false; - mockSpanner.putStatementResult( - StatementResult.exception( - SELECT1, - mockSpanner.createAbortedException( - ByteString.copyFromUtf8("some-tx")))); - } else { - mockSpanner.putStatementResult( - StatementResult.query(SELECT1, SELECT1_RESULTSET)); + @Test + public void testInlinedBeginAsyncTxWithError() throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + ApiFuture updateCount = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext transaction) { + transaction.executeUpdateAsync(INVALID_UPDATE_STATEMENT); + return transaction.executeUpdateAsync(UPDATE_STATEMENT); + } + }, + executor); + assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); + // The first statement will fail and not return a transaction id. This will trigger a retry of + // the entire transaction, and the retry will do an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + // The first update will start a transaction, but then fail the update statement. This will + // start a transaction on the mock server, but that transaction will never be returned to the + // client. + assertThat(countTransactionsStarted()).isEqualTo(2); + } + + @Test + public void testInlinedBeginAsyncTxWithOnlyMutations() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext transaction) { + transaction.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); + return ApiFutures.immediateFuture(null); + } + }, + executor) + .get(); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testAsyncTransactionManagerInlinedBeginTx() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { + TransactionContextFuture txn = txMgr.beginAsync(); + while (true) { + AsyncTransactionStep updateCount = + txn.then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Void input) + throws Exception { + return txn.executeUpdateAsync(UPDATE_STATEMENT); } - try (ResultSet rs = transaction.executeQuery(SELECT1)) { - while (rs.next()) { - return rs.getLong(0); + }, + executor); + CommitTimestampFuture commitTimestamp = updateCount.commitAsync(); + try { + assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); + assertThat(commitTimestamp.get()).isNotNull(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetryAsync(); + } + } + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testAsyncTransactionManagerInlinedBeginTxAborted() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { + TransactionContextFuture txn = txMgr.beginAsync(); + boolean first = true; + while (true) { + try { + AsyncTransactionStep updateCount = + txn.then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Void input) + throws Exception { + return txn.executeUpdateAsync(UPDATE_STATEMENT); } + }, + executor); + if (first) { + // Abort the transaction after the statement has been executed to ensure that the + // transaction has actually been started before the test tries to abort it. + updateCount.then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Long input) + throws Exception { + mockSpanner.abortAllTransactions(); + return ApiFutures.immediateFuture(null); } - return 0L; - } - }); - assertThat(updateCount).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + }, + MoreExecutors.directExecutor()); + first = false; + } + assertThat(updateCount.commitAsync().get()).isNotNull(); + assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetryAsync(); + } + } + } + // The retry will use a BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(2); + } + + @Test + public void testAsyncTransactionManagerInlinedBeginTxWithOnlyMutations() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { + TransactionContextFuture txn = txMgr.beginAsync(); + while (true) { + try { + txn.then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Void input) + throws Exception { + txn.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); + return ApiFutures.immediateFuture(null); + } + }, + executor) + .commitAsync() + .get(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetryAsync(); + } + } + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testAsyncTransactionManagerInlinedBeginTxWithError() + throws InterruptedException, ExecutionException { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { + TransactionContextFuture txn = txMgr.beginAsync(); + while (true) { + try { + AsyncTransactionStep updateCount = + txn.then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Void input) + throws Exception { + return txn.executeUpdateAsync(INVALID_UPDATE_STATEMENT); + } + }, + executor) + .then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext txn, Long input) + throws Exception { + return txn.executeUpdateAsync(UPDATE_STATEMENT); + } + }, + executor); + try { + updateCount.commitAsync().get(); + fail("missing expected exception"); + } catch (ExecutionException e) { + assertThat(e.getCause()).isInstanceOf(SpannerException.class); + SpannerException se = (SpannerException) e.getCause(); + assertThat(se.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetryAsync(); + } + } + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } } - @Test - public void testInlinedBeginFirstQueryReturnsUnavailable() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - mockSpanner.setExecuteStreamingSqlExecutionTime( - SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 0)); - long value = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - // The first attempt will return UNAVAILABLE and retry internally. - try (ResultSet rs = transaction.executeQuery(SELECT1)) { - while (rs.next()) { - return rs.getLong(0); + @RunWith(JUnit4.class) + public static class InlineBeginTransactionWithoutExecutorTest extends InlineBeginTransactionTest { + @Test + public void testInlinedBeginTx() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + return transaction.executeUpdate(UPDATE_STATEMENT); + } + }); + assertThat(updateCount).isEqualTo(UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testInlinedBeginTxAborted() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + final AtomicBoolean firstAttempt = new AtomicBoolean(true); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + long res = transaction.executeUpdate(UPDATE_STATEMENT); + if (firstAttempt.getAndSet(false)) { + mockSpanner.abortTransaction(transaction); } + return res; } - return 0L; - } - }); - assertThat(value).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - } + }); + assertThat(updateCount).isEqualTo(UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + // We have started 2 transactions, because the first transaction aborted during the commit. + assertThat(countRequests(CommitRequest.class)).isEqualTo(2); + assertThat(countTransactionsStarted()).isEqualTo(2); + } - @Test - public void testInlinedBeginFirstReadReturnsUnavailable() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - mockSpanner.setStreamingReadExecutionTime( - SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 0)); - long value = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - // The first attempt will return UNAVAILABLE and retry internally. - try (ResultSet rs = - transaction.read("FOO", KeySet.all(), Arrays.asList("ID"))) { - while (rs.next()) { - return rs.getLong(0); + @Test + public void testInlinedBeginFirstUpdateAborts() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + boolean firstAttempt = true; + + @Override + public Long run(TransactionContext transaction) throws Exception { + if (firstAttempt) { + firstAttempt = false; + mockSpanner.putStatementResult( + StatementResult.exception( + UPDATE_STATEMENT, + mockSpanner.createAbortedException( + ByteString.copyFromUtf8("some-tx")))); + } else { + mockSpanner.putStatementResult( + StatementResult.update(UPDATE_STATEMENT, UPDATE_COUNT)); } + return transaction.executeUpdate(UPDATE_STATEMENT); } - return 0L; - } - }); - assertThat(value).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ReadRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - } + }); + assertThat(updateCount).isEqualTo(UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithQuery() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - try (ResultSet rs = transaction.executeQuery(SELECT1)) { - while (rs.next()) { - return rs.getLong(0); + @Test + public void testInlinedBeginFirstQueryAborts() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + boolean firstAttempt = true; + + @Override + public Long run(TransactionContext transaction) throws Exception { + if (firstAttempt) { + firstAttempt = false; + mockSpanner.putStatementResult( + StatementResult.exception( + SELECT1, + mockSpanner.createAbortedException( + ByteString.copyFromUtf8("some-tx")))); + } else { + mockSpanner.putStatementResult( + StatementResult.query(SELECT1, SELECT1_RESULTSET)); + } + try (ResultSet rs = transaction.executeQuery(SELECT1)) { + while (rs.next()) { + return rs.getLong(0); + } } + return 0L; } - return 0L; - } - }); - assertThat(updateCount).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + }); + assertThat(updateCount).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + } + + @Test + public void testInlinedBeginFirstQueryReturnsUnavailable() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + mockSpanner.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 0)); + long value = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + // The first attempt will return UNAVAILABLE and retry internally. + try (ResultSet rs = transaction.executeQuery(SELECT1)) { + while (rs.next()) { + return rs.getLong(0); + } + } + return 0L; + } + }); + assertThat(value).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + } + + @Test + public void testInlinedBeginFirstReadReturnsUnavailable() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + mockSpanner.setStreamingReadExecutionTime( + SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 0)); + long value = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + // The first attempt will return UNAVAILABLE and retry internally. + try (ResultSet rs = + transaction.read("FOO", KeySet.all(), Arrays.asList("ID"))) { + while (rs.next()) { + return rs.getLong(0); + } + } + return 0L; + } + }); + assertThat(value).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ReadRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + } + + @Test + public void testInlinedBeginTxWithQuery() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + try (ResultSet rs = transaction.executeQuery(SELECT1)) { + while (rs.next()) { + return rs.getLong(0); + } + } + return 0L; + } + }); + assertThat(updateCount).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testInlinedBeginTxWithRead() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + try (ResultSet rs = + transaction.read("FOO", KeySet.all(), Arrays.asList("ID"))) { + while (rs.next()) { + return rs.getLong(0); + } + } + return 0L; + } + }); + assertThat(updateCount).isEqualTo(1L); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ReadRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testInlinedBeginTxWithBatchDml() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long[] updateCounts = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public long[] run(TransactionContext transaction) throws Exception { + return transaction.batchUpdate( + Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)); + } + }); + assertThat(updateCounts).asList().containsExactly(UPDATE_COUNT, UPDATE_COUNT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void testInlinedBeginTxWithError() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + try { + transaction.executeUpdate(INVALID_UPDATE_STATEMENT); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + return transaction.executeUpdate(UPDATE_STATEMENT); + } + }); + assertThat(updateCount).isEqualTo(UPDATE_COUNT); + // The transaction will be retried because the first statement that also tried to include the + // BeginTransaction statement failed and did not return a transaction. That forces a retry of + // the entire transaction with an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + // The update statement will be executed 3 times: + // 1. The invalid update statement will be executed during the first attempt and fail. The + // second update statement will not be executed, as the transaction runner sees that the + // initial + // statement failed and did not return a valid transaction id. + // 2. The invalid update statement is executed again during the retry. + // 3. The valid update statement is only executed after the first statement succeeded. + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(3); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + // The first update will start a transaction, but then fail the update statement. This will + // start a transaction on the mock server, but that transaction will never be returned to the + // client. + assertThat(countTransactionsStarted()).isEqualTo(2); + } - @Test - public void testInlinedBeginTxWithRead() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - long updateCount = + @Test + public void testInlinedBeginTxWithErrorOnFirstStatement_andThenErrorOnBeginTransaction() { + mockSpanner.setBeginTransactionExecutionTime( + SimulatedExecutionTime.ofException( + Status.INTERNAL + .withDescription("Begin transaction failed due to an internal error") + .asRuntimeException())); + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try { client .readWriteTransaction() .run( - new TransactionCallable() { + new TransactionCallable() { @Override - public Long run(TransactionContext transaction) throws Exception { - try (ResultSet rs = - transaction.read("FOO", KeySet.all(), Arrays.asList("ID"))) { - while (rs.next()) { - return rs.getLong(0); - } + public Void run(TransactionContext transaction) throws Exception { + try { + transaction.executeUpdate(INVALID_UPDATE_STATEMENT); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); } - return 0L; + return null; } }); - assertThat(updateCount).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ReadRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + fail("Missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INTERNAL); + assertThat(e.getMessage()).contains("Begin transaction failed due to an internal error"); + } + // The transaction will be retried because the first statement that also tried to include the + // BeginTransaction statement failed and did not return a transaction. That forces a retry of + // the entire transaction with an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + // The explicit BeginTransaction RPC failed, so only one transaction was started. + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithBatchDml() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long[] updateCounts = + @Test + public void testInlinedBeginTxWithUncaughtError() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try { client .readWriteTransaction() .run( - new TransactionCallable() { + new TransactionCallable() { @Override - public long[] run(TransactionContext transaction) throws Exception { - return transaction.batchUpdate( - Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)); + public Long run(TransactionContext transaction) throws Exception { + return transaction.executeUpdate(INVALID_UPDATE_STATEMENT); } }); - assertThat(updateCounts).asList().containsExactly(UPDATE_COUNT, UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + // The first update will start a transaction, but then fail the update statement. This will + // start a transaction on the mock server, but that transaction will never be returned to the + // client. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + // No rollback request will be initiated because the client does not receive any transaction + // id. + assertThat(countRequests(RollbackRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithError() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long updateCount = + @Test + public void testInlinedBeginTxWithUncaughtErrorAfterSuccessfulBegin() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try { client .readWriteTransaction() .run( new TransactionCallable() { @Override public Long run(TransactionContext transaction) throws Exception { - try { - transaction.executeUpdate(INVALID_UPDATE_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - return transaction.executeUpdate(UPDATE_STATEMENT); + // This statement will start a transaction. + transaction.executeUpdate(UPDATE_STATEMENT); + // This statement will fail and cause a rollback as the exception is not caught. + return transaction.executeUpdate(INVALID_UPDATE_STATEMENT); } }); - assertThat(updateCount).isEqualTo(UPDATE_COUNT); - // The transaction will be retried because the first statement that also tried to include the - // BeginTransaction statement failed and did not return a transaction. That forces a retry of - // the entire transaction with an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - // The update statement will be executed 3 times: - // 1. The invalid update statement will be executed during the first attempt and fail. The - // second update statement will not be executed, as the transaction runner sees that the initial - // statement failed and did not return a valid transaction id. - // 2. The invalid update statement is executed again during the retry. - // 3. The valid update statement is only executed after the first statement succeeded. - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(3); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - // The first update will start a transaction, but then fail the update statement. This will - // start a transaction on the mock server, but that transaction will never be returned to the - // client. - assertThat(countTransactionsStarted()).isEqualTo(2); - } + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(RollbackRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithErrorOnFirstStatement_andThenErrorOnBeginTransaction() { - mockSpanner.setBeginTransactionExecutionTime( - SimulatedExecutionTime.ofException( - Status.INTERNAL - .withDescription("Begin transaction failed due to an internal error") - .asRuntimeException())); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try { - transaction.executeUpdate(INVALID_UPDATE_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - return null; - } - }); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INTERNAL); - assertThat(e.getMessage()).contains("Begin transaction failed due to an internal error"); + @Test + public void testInlinedBeginTxBatchDmlWithErrorOnFirstStatement() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + Void res = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + try { + transaction.batchUpdate( + ImmutableList.of(INVALID_UPDATE_STATEMENT, UPDATE_STATEMENT)); + fail("missing expected exception"); + } catch (SpannerBatchUpdateException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + assertThat(e.getUpdateCounts()).hasLength(0); + } + return null; + } + }); + assertThat(res).isNull(); + // The first statement failed and could not return a transaction. The entire transaction is + // therefore retried with an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(2); } - // The transaction will be retried because the first statement that also tried to include the - // BeginTransaction statement failed and did not return a transaction. That forces a retry of - // the entire transaction with an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - // The explicit BeginTransaction RPC failed, so only one transaction was started. - assertThat(countTransactionsStarted()).isEqualTo(1); - } - @Test - public void testInlinedBeginTxWithUncaughtError() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - return transaction.executeUpdate(INVALID_UPDATE_STATEMENT); - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - // The first update will start a transaction, but then fail the update statement. This will - // start a transaction on the mock server, but that transaction will never be returned to the - // client. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - // No rollback request will be initiated because the client does not receive any transaction id. - assertThat(countRequests(RollbackRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginTxWithUncaughtErrorAfterSuccessfulBegin() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - // This statement will start a transaction. - transaction.executeUpdate(UPDATE_STATEMENT); - // This statement will fail and cause a rollback as the exception is not caught. - return transaction.executeUpdate(INVALID_UPDATE_STATEMENT); - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(RollbackRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginTxBatchDmlWithErrorOnFirstStatement() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - Void res = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try { - transaction.batchUpdate( - ImmutableList.of(INVALID_UPDATE_STATEMENT, UPDATE_STATEMENT)); - fail("missing expected exception"); - } catch (SpannerBatchUpdateException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - assertThat(e.getUpdateCounts()).hasLength(0); - } - return null; - } - }); - assertThat(res).isNull(); - // The first statement failed and could not return a transaction. The entire transaction is - // therefore retried with an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(2); - } - - @Test - public void testInlinedBeginTxBatchDmlWithErrorOnSecondStatement() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - try { - transaction.batchUpdate( - ImmutableList.of(UPDATE_STATEMENT, INVALID_UPDATE_STATEMENT)); - fail("missing expected exception"); - // The following line is needed as the compiler does not know that this is - // unreachable. - return -1L; - } catch (SpannerBatchUpdateException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - assertThat(e.getUpdateCounts()).hasLength(1); - return e.getUpdateCounts()[0]; + @Test + public void testInlinedBeginTxBatchDmlWithErrorOnSecondStatement() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + try { + transaction.batchUpdate( + ImmutableList.of(UPDATE_STATEMENT, INVALID_UPDATE_STATEMENT)); + fail("missing expected exception"); + // The following line is needed as the compiler does not know that this is + // unreachable. + return -1L; + } catch (SpannerBatchUpdateException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + assertThat(e.getUpdateCounts()).hasLength(1); + return e.getUpdateCounts()[0]; + } } - } - }); - assertThat(updateCount).isEqualTo(UPDATE_COUNT); - // Although the batch DML returned an error, that error was for the second statement. That means - // that the transaction was started by the first statement. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + }); + assertThat(updateCount).isEqualTo(UPDATE_COUNT); + // Although the batch DML returned an error, that error was for the second statement. That + // means + // that the transaction was started by the first statement. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithErrorOnStreamingSql() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - Void res = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try (ResultSet rs = transaction.executeQuery(INVALID_SELECT)) { - while (rs.next()) {} - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + @Test + public void testInlinedBeginTxWithErrorOnStreamingSql() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + Void res = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + try (ResultSet rs = transaction.executeQuery(INVALID_SELECT)) { + while (rs.next()) {} + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + return null; } - return null; - } - }); - assertThat(res).isNull(); - // The transaction will be retried because the first statement that also tried to include the - // BeginTransaction statement failed and did not return a transaction. That forces a retry of - // the entire transaction with an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - // The first update will start a transaction, but then fail the update statement. This will - // start a transaction on the mock server, but that transaction will never be returned to the - // client. - assertThat(countTransactionsStarted()).isEqualTo(2); - } + }); + assertThat(res).isNull(); + // The transaction will be retried because the first statement that also tried to include the + // BeginTransaction statement failed and did not return a transaction. That forces a retry of + // the entire transaction with an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + // The first update will start a transaction, but then fail the update statement. This will + // start a transaction on the mock server, but that transaction will never be returned to the + // client. + assertThat(countTransactionsStarted()).isEqualTo(2); + } - @Test - public void testInlinedBeginTxWithErrorOnSecondPartialResultSet() { - final Statement statement = Statement.of("SELECT * FROM BROKEN_TABLE"); - RandomResultSetGenerator generator = new RandomResultSetGenerator(2); - mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate())); - // The first PartialResultSet will be returned successfully, and then a DATA_LOSS exception will - // be returned. - mockSpanner.setExecuteStreamingSqlExecutionTime( - SimulatedExecutionTime.ofStreamException(Status.DATA_LOSS.asRuntimeException(), 1)); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - Void res = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try (ResultSet rs = transaction.executeQuery(statement)) { - while (rs.next()) {} - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.DATA_LOSS); + @Test + public void testInlinedBeginTxWithErrorOnSecondPartialResultSet() { + final Statement statement = Statement.of("SELECT * FROM BROKEN_TABLE"); + RandomResultSetGenerator generator = new RandomResultSetGenerator(2); + mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate())); + // The first PartialResultSet will be returned successfully, and then a DATA_LOSS exception + // will + // be returned. + mockSpanner.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStreamException(Status.DATA_LOSS.asRuntimeException(), 1)); + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + Void res = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + try (ResultSet rs = transaction.executeQuery(statement)) { + while (rs.next()) {} + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.DATA_LOSS); + } + return null; } - return null; - } - }); - assertThat(res).isNull(); - // The transaction will not be retried, as the first PartialResultSet returns the transaction - // ID, and the second fails with an error code. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + }); + assertThat(res).isNull(); + // The transaction will not be retried, as the first PartialResultSet returns the transaction + // ID, and the second fails with an error code. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithParallelQueries() { - final int numQueries = 100; - final ScheduledExecutorService executor = Executors.newScheduledThreadPool(16); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - long updateCount = - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(final TransactionContext transaction) throws Exception { - List> futures = new ArrayList<>(numQueries); - for (int i = 0; i < numQueries; i++) { - futures.add( - executor.submit( - new Callable() { - @Override - public Long call() throws Exception { - try (ResultSet rs = transaction.executeQuery(SELECT1)) { - while (rs.next()) { - return rs.getLong(0); + @Test + public void testInlinedBeginTxWithParallelQueries() { + final int numQueries = 100; + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(16); + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + long updateCount = + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(final TransactionContext transaction) throws Exception { + List> futures = new ArrayList<>(numQueries); + for (int i = 0; i < numQueries; i++) { + futures.add( + executor.submit( + new Callable() { + @Override + public Long call() throws Exception { + try (ResultSet rs = transaction.executeQuery(SELECT1)) { + while (rs.next()) { + return rs.getLong(0); + } } + return 0L; } - return 0L; - } - })); - } - Long res = 0L; - for (Future f : futures) { - res += f.get(); + })); + } + Long res = 0L; + for (Future f : futures) { + res += f.get(); + } + return res; } - return res; - } - }); - assertThat(updateCount).isEqualTo(1L * numQueries); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + }); + assertThat(updateCount).isEqualTo(1L * numQueries); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @Test - public void testInlinedBeginTxWithOnlyMutations() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - transaction.buffer( - Arrays.asList( - Mutation.newInsertBuilder("FOO").set("ID").to(1L).build(), - Mutation.delete("FOO", Key.of(1L)))); - return null; - } - }); - // There should be 1 call to BeginTransaction because there is no statement that we can use to - // inline the BeginTransaction call with. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } + @Test + public void testInlinedBeginTxWithOnlyMutations() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.buffer( + Arrays.asList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).build(), + Mutation.delete("FOO", Key.of(1L)))); + return null; + } + }); + // There should be 1 call to BeginTransaction because there is no statement that we can use to + // inline the BeginTransaction call with. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - @SuppressWarnings("resource") - @Test - public void testTransactionManagerInlinedBeginTx() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (TransactionManager txMgr = client.transactionManager()) { - TransactionContext txn = txMgr.begin(); - while (true) { - try { - assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); - txMgr.commit(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetry(); + @SuppressWarnings("resource") + @Test + public void testTransactionManagerInlinedBeginTx() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager txMgr = client.transactionManager()) { + TransactionContext txn = txMgr.begin(); + while (true) { + try { + assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); + txMgr.commit(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetry(); + } } } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - @SuppressWarnings("resource") - @Test - public void testTransactionManagerInlinedBeginTxAborted() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (TransactionManager txMgr = client.transactionManager()) { - TransactionContext txn = txMgr.begin(); - boolean first = true; - while (true) { - try { - assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); - if (first) { - mockSpanner.abortAllTransactions(); - first = false; + @SuppressWarnings("resource") + @Test + public void testTransactionManagerInlinedBeginTxAborted() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager txMgr = client.transactionManager()) { + TransactionContext txn = txMgr.begin(); + boolean first = true; + while (true) { + try { + assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); + if (first) { + mockSpanner.abortAllTransactions(); + first = false; + } + txMgr.commit(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetry(); } - txMgr.commit(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetry(); } } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(2); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(2); - } - @SuppressWarnings("resource") - @Test - public void testTransactionManagerInlinedBeginTxWithOnlyMutations() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (TransactionManager txMgr = client.transactionManager()) { - TransactionContext txn = txMgr.begin(); - while (true) { - try { - txn.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); - txMgr.commit(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetry(); + @SuppressWarnings("resource") + @Test + public void testTransactionManagerInlinedBeginTxWithOnlyMutations() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager txMgr = client.transactionManager()) { + TransactionContext txn = txMgr.begin(); + while (true) { + try { + txn.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); + txMgr.commit(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetry(); + } } } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - @SuppressWarnings("resource") - @Test - public void testTransactionManagerInlinedBeginTxWithError() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (TransactionManager txMgr = client.transactionManager()) { - TransactionContext txn = txMgr.begin(); - while (true) { - try { + @SuppressWarnings("resource") + @Test + public void testTransactionManagerInlinedBeginTxWithError() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager txMgr = client.transactionManager()) { + TransactionContext txn = txMgr.begin(); + while (true) { try { - txn.executeUpdate(INVALID_UPDATE_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + try { + txn.executeUpdate(INVALID_UPDATE_STATEMENT); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); + txMgr.commit(); + break; + } catch (AbortedException e) { + txn = txMgr.resetForRetry(); } - assertThat(txn.executeUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); - txMgr.commit(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetry(); } } + // The first statement will fail and not return a transaction id. This will trigger a retry of + // the entire transaction, and the retry will do an explicit BeginTransaction RPC. + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); + // The first statement will start a transaction, but it will never be returned to the client + // as + // the update statement fails. + assertThat(countTransactionsStarted()).isEqualTo(2); } - // The first statement will fail and not return a transaction id. This will trigger a retry of - // the entire transaction, and the retry will do an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - // The first statement will start a transaction, but it will never be returned to the client as - // the update statement fails. - assertThat(countTransactionsStarted()).isEqualTo(2); - } - @SuppressWarnings("resource") - @Test - public void testTransactionManagerInlinedBeginTxWithUncaughtError() { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (TransactionManager txMgr = client.transactionManager()) { - TransactionContext txn = txMgr.begin(); - while (true) { - try { - txn.executeUpdate(INVALID_UPDATE_STATEMENT); - fail("missing expected exception"); - } catch (AbortedException e) { - txn = txMgr.resetForRetry(); + @SuppressWarnings("resource") + @Test + public void testTransactionManagerInlinedBeginTxWithUncaughtError() { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager txMgr = client.transactionManager()) { + TransactionContext txn = txMgr.begin(); + while (true) { + try { + txn.executeUpdate(INVALID_UPDATE_STATEMENT); + fail("missing expected exception"); + } catch (AbortedException e) { + txn = txMgr.resetForRetry(); + } } + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); } - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginAsyncTx() throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - ApiFuture updateCount = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext txn) { - return txn.executeUpdateAsync(UPDATE_STATEMENT); - } - }, - executor); - assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginAsyncTxAborted() throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - final AtomicBoolean firstAttempt = new AtomicBoolean(true); - ApiFuture updateCount = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext txn) { - ApiFuture res = txn.executeUpdateAsync(UPDATE_STATEMENT); - if (firstAttempt.getAndSet(false)) { - mockSpanner.abortTransaction(txn); - } - return res; - } - }, - executor); - assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - // We have started 2 transactions, because the first transaction aborted. - assertThat(countTransactionsStarted()).isEqualTo(2); - } - @Test - public void testInlinedBeginAsyncTxWithQuery() throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); - ApiFuture updateCount = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext txn) { - final SettableApiFuture res = SettableApiFuture.create(); - try (AsyncResultSet rs = txn.executeQueryAsync(SELECT1)) { - rs.setCallback( - executor, - new ReadyCallback() { - @Override - public CallbackResponse cursorReady(AsyncResultSet resultSet) { - switch (resultSet.tryNext()) { - case DONE: - return CallbackResponse.DONE; - case NOT_READY: - return CallbackResponse.CONTINUE; - case OK: - res.set(resultSet.getLong(0)); - default: - throw new IllegalStateException(); - } - } - }); - } - return res; - } - }, - queryExecutor); - assertThat(updateCount.get()).isEqualTo(1L); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - queryExecutor.shutdown(); - } - - @Test - public void testInlinedBeginAsyncTxWithBatchDml() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - ApiFuture updateCounts = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext transaction) { - return transaction.batchUpdateAsync( - Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)); - } - }, - executor); - assertThat(updateCounts.get()).asList().containsExactly(UPDATE_COUNT, UPDATE_COUNT); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginAsyncTxWithError() throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - ApiFuture updateCount = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext transaction) { - transaction.executeUpdateAsync(INVALID_UPDATE_STATEMENT); - return transaction.executeUpdateAsync(UPDATE_STATEMENT); - } - }, - executor); - assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); - // The first statement will fail and not return a transaction id. This will trigger a retry of - // the entire transaction, and the retry will do an explicit BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - // The first update will start a transaction, but then fail the update statement. This will - // start a transaction on the mock server, but that transaction will never be returned to the - // client. - assertThat(countTransactionsStarted()).isEqualTo(2); - } - - @Test - public void testInlinedBeginAsyncTxWithParallelQueries() - throws InterruptedException, ExecutionException { - final int numQueries = 100; - final ScheduledExecutorService executor = Executors.newScheduledThreadPool(16); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - ApiFuture updateCount = - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(final TransactionContext txn) { - List> futures = new ArrayList<>(numQueries); - for (int i = 0; i < numQueries; i++) { - final SettableApiFuture res = SettableApiFuture.create(); - try (AsyncResultSet rs = txn.executeQueryAsync(SELECT1)) { - rs.setCallback( - executor, - new ReadyCallback() { - @Override - public CallbackResponse cursorReady(AsyncResultSet resultSet) { - switch (resultSet.tryNext()) { - case DONE: - return CallbackResponse.DONE; - case NOT_READY: - return CallbackResponse.CONTINUE; - case OK: - res.set(resultSet.getLong(0)); - default: - throw new IllegalStateException(); + @Test + public void testInlinedBeginAsyncTxWithParallelQueries() + throws InterruptedException, ExecutionException { + final int numQueries = 100; + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(16); + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + ApiFuture updateCount = + client + .runAsync() + .runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(final TransactionContext txn) { + List> futures = new ArrayList<>(numQueries); + for (int i = 0; i < numQueries; i++) { + final SettableApiFuture res = SettableApiFuture.create(); + try (AsyncResultSet rs = txn.executeQueryAsync(SELECT1)) { + rs.setCallback( + executor, + new ReadyCallback() { + @Override + public CallbackResponse cursorReady(AsyncResultSet resultSet) { + switch (resultSet.tryNext()) { + case DONE: + return CallbackResponse.DONE; + case NOT_READY: + return CallbackResponse.CONTINUE; + case OK: + res.set(resultSet.getLong(0)); + default: + throw new IllegalStateException(); + } } - } - }); + }); + } + futures.add(res); } - futures.add(res); - } - return ApiFutures.transformAsync( - ApiFutures.allAsList(futures), - new ApiAsyncFunction, Long>() { - @Override - public ApiFuture apply(List input) throws Exception { - long sum = 0L; - for (Long l : input) { - sum += l; + return ApiFutures.transformAsync( + ApiFutures.allAsList(futures), + new ApiAsyncFunction, Long>() { + @Override + public ApiFuture apply(List input) throws Exception { + long sum = 0L; + for (Long l : input) { + sum += l; + } + return ApiFutures.immediateFuture(sum); } - return ApiFutures.immediateFuture(sum); - } - }, - MoreExecutors.directExecutor()); - } - }, - executor); - assertThat(updateCount.get()).isEqualTo(1L * numQueries); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testInlinedBeginAsyncTxWithOnlyMutations() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - client - .runAsync() - .runAsync( - new AsyncWork() { - @Override - public ApiFuture doWorkAsync(TransactionContext transaction) { - transaction.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); - return ApiFutures.immediateFuture(null); - } - }, - executor) - .get(); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testAsyncTransactionManagerInlinedBeginTx() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { - TransactionContextFuture txn = txMgr.beginAsync(); - while (true) { - AsyncTransactionStep updateCount = - txn.then( - new AsyncTransactionFunction() { - @Override - public ApiFuture apply(TransactionContext txn, Void input) - throws Exception { - return txn.executeUpdateAsync(UPDATE_STATEMENT); - } - }, - executor); - CommitTimestampFuture commitTimestamp = updateCount.commitAsync(); - try { - assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); - assertThat(commitTimestamp.get()).isNotNull(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetryAsync(); - } - } - } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void testAsyncTransactionManagerInlinedBeginTxAborted() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { - TransactionContextFuture txn = txMgr.beginAsync(); - boolean first = true; - while (true) { - try { - AsyncTransactionStep updateCount = - txn.then( - new AsyncTransactionFunction() { - @Override - public ApiFuture apply(TransactionContext txn, Void input) - throws Exception { - return txn.executeUpdateAsync(UPDATE_STATEMENT); + }, + MoreExecutors.directExecutor()); } }, executor); - if (first) { - // Abort the transaction after the statement has been executed to ensure that the - // transaction has actually been started before the test tries to abort it. - updateCount.then( - new AsyncTransactionFunction() { - @Override - public ApiFuture apply(TransactionContext txn, Long input) - throws Exception { - mockSpanner.abortAllTransactions(); - return ApiFutures.immediateFuture(null); - } - }, - MoreExecutors.directExecutor()); - first = false; - } - assertThat(updateCount.commitAsync().get()).isNotNull(); - assertThat(updateCount.get()).isEqualTo(UPDATE_COUNT); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetryAsync(); - } - } + assertThat(updateCount.get()).isEqualTo(1L * numQueries); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countTransactionsStarted()).isEqualTo(1); } - // The retry will use a BeginTransaction RPC. - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(2); - } - @Test - public void testAsyncTransactionManagerInlinedBeginTxWithOnlyMutations() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { - TransactionContextFuture txn = txMgr.beginAsync(); - while (true) { - try { - txn.then( - new AsyncTransactionFunction() { - @Override - public ApiFuture apply(TransactionContext txn, Void input) - throws Exception { - txn.buffer(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build()); - return ApiFutures.immediateFuture(null); - } - }, - executor) - .commitAsync() - .get(); - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetryAsync(); - } - } + @Test + public void queryWithoutNext() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + assertThat( + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + // This will not actually send an RPC, so it will also not request a + // transaction. + transaction.executeQuery(SELECT1); + return transaction.executeUpdate(UPDATE_STATEMENT); + } + })) + .isEqualTo(UPDATE_COUNT); + assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); + assertThat(countTransactionsStarted()).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - @Test - public void testAsyncTransactionManagerInlinedBeginTxWithError() - throws InterruptedException, ExecutionException { - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - try (AsyncTransactionManager txMgr = client.transactionManagerAsync()) { - TransactionContextFuture txn = txMgr.beginAsync(); - while (true) { - try { - AsyncTransactionStep updateCount = - txn.then( - new AsyncTransactionFunction() { + @Test + public void queryAsyncWithoutCallback() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + assertThat( + client + .readWriteTransaction() + .run( + new TransactionCallable() { @Override - public ApiFuture apply(TransactionContext txn, Void input) - throws Exception { - return txn.executeUpdateAsync(INVALID_UPDATE_STATEMENT); + public Long run(TransactionContext transaction) throws Exception { + transaction.executeQueryAsync(SELECT1); + return transaction.executeUpdate(UPDATE_STATEMENT); } - }, - executor) - .then( - new AsyncTransactionFunction() { + })) + .isEqualTo(UPDATE_COUNT); + assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); + assertThat(countTransactionsStarted()).isEqualTo(1); + } + + @Test + public void readWithoutNext() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + assertThat( + client + .readWriteTransaction() + .run( + new TransactionCallable() { @Override - public ApiFuture apply(TransactionContext txn, Long input) - throws Exception { - return txn.executeUpdateAsync(UPDATE_STATEMENT); + public Long run(TransactionContext transaction) throws Exception { + transaction.read("FOO", KeySet.all(), Arrays.asList("ID")); + return transaction.executeUpdate(UPDATE_STATEMENT); } - }, - executor); - try { - updateCount.commitAsync().get(); - fail("missing expected exception"); - } catch (ExecutionException e) { - assertThat(e.getCause()).isInstanceOf(SpannerException.class); - SpannerException se = (SpannerException) e.getCause(); - assertThat(se.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - break; - } catch (AbortedException e) { - txn = txMgr.resetForRetryAsync(); - } - } + })) + .isEqualTo(UPDATE_COUNT); + assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ReadRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); + assertThat(countTransactionsStarted()).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void queryWithoutNext() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - assertThat( - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - // This will not actually send an RPC, so it will also not request a - // transaction. - transaction.executeQuery(SELECT1); - return transaction.executeUpdate(UPDATE_STATEMENT); - } - })) - .isEqualTo(UPDATE_COUNT); - assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void queryAsyncWithoutCallback() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - assertThat( - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - transaction.executeQueryAsync(SELECT1); - return transaction.executeUpdate(UPDATE_STATEMENT); - } - })) - .isEqualTo(UPDATE_COUNT); - assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void readWithoutNext() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - assertThat( - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - transaction.read("FOO", KeySet.all(), Arrays.asList("ID")); - return transaction.executeUpdate(UPDATE_STATEMENT); - } - })) - .isEqualTo(UPDATE_COUNT); - assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ReadRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void readAsyncWithoutCallback() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - assertThat( - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - transaction.readAsync("FOO", KeySet.all(), Arrays.asList("ID")); - return transaction.executeUpdate(UPDATE_STATEMENT); - } - })) - .isEqualTo(UPDATE_COUNT); - assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ReadRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); - assertThat(countTransactionsStarted()).isEqualTo(1); - } - - @Test - public void query_ThenUpdate_ThenConsumeResultSet() - throws InterruptedException, TimeoutException { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - assertThat( - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - ResultSet rs = transaction.executeQuery(SELECT1); - long updateCount = transaction.executeUpdate(UPDATE_STATEMENT); - // Consume the result set. - while (rs.next()) {} - return updateCount; - } - })) - .isEqualTo(UPDATE_COUNT); - // The update statement should start the transaction, and the query should use the transaction - // id returned by the update. - assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); - assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(2L); - assertThat(mockSpanner.countRequestsOfType(CommitRequest.class)).isEqualTo(1L); - assertThat(countTransactionsStarted()).isEqualTo(1); - List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); - assertThat(requests.get(0)).isInstanceOf(ExecuteSqlRequest.class); - assertThat(((ExecuteSqlRequest) requests.get(0)).getSql()).isEqualTo(UPDATE_STATEMENT.getSql()); - assertThat(requests.get(1)).isInstanceOf(ExecuteSqlRequest.class); - assertThat(((ExecuteSqlRequest) requests.get(1)).getSql()).isEqualTo(SELECT1.getSql()); - } - - @Test - public void testInlinedBeginTxWithStreamRetry() { - mockSpanner.setExecuteStreamingSqlExecutionTime( - SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 1)); - - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try (ResultSet rs = transaction.executeQuery(SELECT1_UNION_ALL_SELECT2)) { - while (rs.next()) {} - } - return null; - } - }); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - assertThat(countTransactionsStarted()).isEqualTo(1); - - List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); - assertThat(requests.get(0)).isInstanceOf(ExecuteSqlRequest.class); - ExecuteSqlRequest request1 = (ExecuteSqlRequest) requests.get(0); - assertThat(request1.getSql()).isEqualTo(SELECT1_UNION_ALL_SELECT2.getSql()); - assertThat(request1.getTransaction().getBegin().hasReadWrite()).isTrue(); - assertThat(request1.getTransaction().getId()).isEqualTo(ByteString.EMPTY); - assertThat(request1.getResumeToken()).isEqualTo(ByteString.EMPTY); - - assertThat(requests.get(1)).isInstanceOf(ExecuteSqlRequest.class); - ExecuteSqlRequest request2 = (ExecuteSqlRequest) requests.get(1); - assertThat(request2.getSql()).isEqualTo(SELECT1_UNION_ALL_SELECT2.getSql()); - assertThat(request2.getTransaction().hasBegin()).isFalse(); - assertThat(request2.getTransaction().getId()).isNotEqualTo(ByteString.EMPTY); - assertThat(request2.getResumeToken()).isNotEqualTo(ByteString.EMPTY); - } - @Test - public void testWaitForTransactionTimeout() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - client - .readWriteTransaction() - .run( - new TransactionCallable() { - int attempt = 0; - - @Override - public Void run(TransactionContext transaction) throws Exception { - attempt++; - TransactionContextImpl impl = (TransactionContextImpl) transaction; - if (attempt == 1) { - impl.waitForTransactionTimeoutMillis = 1L; - // Freeze the mock server to prevent the first (async) statement from returning a - // transaction. - mockSpanner.freeze(); - } else { - impl.waitForTransactionTimeoutMillis = 60_000L; - } - transaction.executeUpdateAsync(UPDATE_STATEMENT); - - // Try to execute a query. This will timeout during the first attempt while waiting - // for the first statement to return a transaction, and then force a retry of the - // transaction. - try (ResultSet rs = transaction.executeQuery(SELECT1)) { - while (rs.next()) {} - } catch (Throwable t) { - mockSpanner.unfreeze(); - throw t; - } - return null; - } - }); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(3); - assertThat(countRequests(CommitRequest.class)).isEqualTo(1); - } - - @Test - public void testCloseResultSetWhileRequestInFlight() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - final ExecutorService service = Executors.newSingleThreadExecutor(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - final ResultSet rs = transaction.executeQuery(SELECT1); - // Prevent the server from executing the query. - mockSpanner.freeze(); - service.submit( - new Runnable() { + @Test + public void readAsyncWithoutCallback() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + assertThat( + client + .readWriteTransaction() + .run( + new TransactionCallable() { @Override - public void run() { - // This call will be stuck on the server until the mock server is - // unfrozen. - rs.next(); + public Long run(TransactionContext transaction) throws Exception { + transaction.readAsync("FOO", KeySet.all(), Arrays.asList("ID")); + return transaction.executeUpdate(UPDATE_STATEMENT); } - }); + })) + .isEqualTo(UPDATE_COUNT); + assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ReadRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(1L); + assertThat(countTransactionsStarted()).isEqualTo(1); + } - // Close the result set while the request is in flight. - mockSpanner.waitForRequestsToContain( - new Predicate() { + @Test + public void query_ThenUpdate_ThenConsumeResultSet() + throws InterruptedException, TimeoutException { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + assertThat( + client + .readWriteTransaction() + .run( + new TransactionCallable() { @Override - public boolean apply(AbstractMessage input) { - return input instanceof ExecuteSqlRequest - && ((ExecuteSqlRequest) input).getTransaction().hasBegin(); + public Long run(TransactionContext transaction) throws Exception { + ResultSet rs = transaction.executeQuery(SELECT1); + long updateCount = transaction.executeUpdate(UPDATE_STATEMENT); + // Consume the result set. + while (rs.next()) {} + return updateCount; } - }, - 100L); - rs.close(); - // The next statement should now fail before it is sent to the server because the - // first statement failed to return a transaction while the result set was still - // open. - mockSpanner.unfreeze(); - try { - transaction.executeUpdate(UPDATE_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()) - .contains("ResultSet was closed before a transaction id was returned"); - } - return null; - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - // The commit request will also fail, which means that the entire transaction will fail. - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()) - .contains("ResultSet was closed before a transaction id was returned"); + })) + .isEqualTo(UPDATE_COUNT); + // The update statement should start the transaction, and the query should use the transaction + // id returned by the update. + assertThat(mockSpanner.countRequestsOfType(BeginTransactionRequest.class)).isEqualTo(0L); + assertThat(mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)).isEqualTo(2L); + assertThat(mockSpanner.countRequestsOfType(CommitRequest.class)).isEqualTo(1L); + assertThat(countTransactionsStarted()).isEqualTo(1); + List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertThat(requests.get(0)).isInstanceOf(ExecuteSqlRequest.class); + assertThat(((ExecuteSqlRequest) requests.get(0)).getSql()) + .isEqualTo(UPDATE_STATEMENT.getSql()); + assertThat(requests.get(1)).isInstanceOf(ExecuteSqlRequest.class); + assertThat(((ExecuteSqlRequest) requests.get(1)).getSql()).isEqualTo(SELECT1.getSql()); } - service.shutdown(); - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testQueryWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { + @Test + public void testInlinedBeginTxWithStreamRetry() { + mockSpanner.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStreamException(Status.UNAVAILABLE.asRuntimeException(), 1)); + + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); client .readWriteTransaction() .run( @@ -1564,202 +1436,357 @@ public Void run(TransactionContext transaction) throws Exception { return null; } }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(2); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); + assertThat(countTransactionsStarted()).isEqualTo(1); + + List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertThat(requests.get(0)).isInstanceOf(ExecuteSqlRequest.class); + ExecuteSqlRequest request1 = (ExecuteSqlRequest) requests.get(0); + assertThat(request1.getSql()).isEqualTo(SELECT1_UNION_ALL_SELECT2.getSql()); + assertThat(request1.getTransaction().getBegin().hasReadWrite()).isTrue(); + assertThat(request1.getTransaction().getId()).isEqualTo(ByteString.EMPTY); + assertThat(request1.getResumeToken()).isEqualTo(ByteString.EMPTY); + + assertThat(requests.get(1)).isInstanceOf(ExecuteSqlRequest.class); + ExecuteSqlRequest request2 = (ExecuteSqlRequest) requests.get(1); + assertThat(request2.getSql()).isEqualTo(SELECT1_UNION_ALL_SELECT2.getSql()); + assertThat(request2.getTransaction().hasBegin()).isFalse(); + assertThat(request2.getTransaction().getId()).isNotEqualTo(ByteString.EMPTY); + assertThat(request2.getResumeToken()).isNotEqualTo(ByteString.EMPTY); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testReadWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { + @Test + public void testWaitForTransactionTimeout() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); client .readWriteTransaction() .run( new TransactionCallable() { + int attempt = 0; + @Override public Void run(TransactionContext transaction) throws Exception { - transaction.readRow("FOO", Key.of(1L), Arrays.asList("BAR")); + attempt++; + TransactionContextImpl impl = (TransactionContextImpl) transaction; + if (attempt == 1) { + impl.waitForTransactionTimeoutMillis = 1L; + // Freeze the mock server to prevent the first (async) statement from returning + // a transaction. + mockSpanner.freeze(); + } else { + impl.waitForTransactionTimeoutMillis = 60_000L; + } + ApiFuture updateCount = transaction.executeUpdateAsync(UPDATE_STATEMENT); + + // Try to execute a query. This will timeout during the first attempt while + // waiting + // for the first statement to return a transaction, and then force a retry of the + // transaction. + try (ResultSet rs = transaction.executeQuery(SELECT1)) { + while (rs.next()) {} + } catch (Throwable t) { + mockSpanner.unfreeze(); + // Wait until the update actually finishes so it has returned a transaction. + // This ensures that the retry does not issue a BeginTransaction RPC. + SpannerApiFutures.get(updateCount); + throw t; + } return null; } }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(3); + assertThat(countRequests(CommitRequest.class)).isEqualTo(1); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ReadRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testUpdateWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - transaction.executeUpdate(UPDATE_STATEMENT); - return null; - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + @Test + public void testCloseResultSetWhileRequestInFlight() throws Exception { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + final ExecutorService service = Executors.newSingleThreadExecutor(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + final ResultSet rs = transaction.executeQuery(SELECT1); + // Prevent the server from executing the query. + final CountDownLatch latch = new CountDownLatch(1); + mockSpanner.freeze(); + service.submit( + new Runnable() { + @Override + public void run() { + try { + // This call will be stuck on the server until the mock server is + // unfrozen. + rs.next(); + } finally { + latch.countDown(); + } + } + }); + + // First wait for the request to be on the server and then close the result set + // while the request is in flight. + mockSpanner.waitForRequestsToContain( + new Predicate() { + @Override + public boolean apply(AbstractMessage input) { + return input instanceof ExecuteSqlRequest + && ((ExecuteSqlRequest) input).getTransaction().hasBegin(); + } + }, + 1000L); + rs.close(); + // The next statement should now fail before it is sent to the server because + // the first statement failed to return a transaction while the result set was + // still open. + mockSpanner.unfreeze(); + latch.await(1L, TimeUnit.SECONDS); + try { + transaction.executeUpdate(UPDATE_STATEMENT); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()) + .contains("ResultSet was closed before a transaction id was returned"); + } + return null; + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + // The commit request will also fail, which means that the entire transaction will fail. + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()) + .contains("ResultSet was closed before a transaction id was returned"); + } + service.shutdown(); + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testBatchUpdateWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - transaction.batchUpdate(Arrays.asList(UPDATE_STATEMENT)); - return null; - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + @Test + public void testQueryWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + try (ResultSet rs = transaction.executeQuery(SELECT1_UNION_ALL_SELECT2)) { + while (rs.next()) {} + } + return null; + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testQueryAsyncWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - final ExecutorService executor = Executors.newSingleThreadExecutor(); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Void run(TransactionContext transaction) throws Exception { - try (AsyncResultSet rs = - transaction.executeQueryAsync(SELECT1_UNION_ALL_SELECT2)) { - return SpannerApiFutures.get( - rs.setCallback( - executor, - new ReadyCallback() { - @Override - public CallbackResponse cursorReady(AsyncResultSet resultSet) { - try { - while (true) { - switch (resultSet.tryNext()) { - case OK: - break; - case DONE: - return CallbackResponse.DONE; - case NOT_READY: - return CallbackResponse.CONTINUE; + @Test + public void testReadWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.readRow("FOO", Key.of(1L), Arrays.asList("BAR")); + return null; + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ReadRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + } + + @Test + public void testUpdateWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.executeUpdate(UPDATE_STATEMENT); + return null; + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + } + + @Test + public void testBatchUpdateWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.batchUpdate(Arrays.asList(UPDATE_STATEMENT)); + return null; + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); + } + + @Test + public void testQueryAsyncWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + try (AsyncResultSet rs = + transaction.executeQueryAsync(SELECT1_UNION_ALL_SELECT2)) { + return SpannerApiFutures.get( + rs.setCallback( + executor, + new ReadyCallback() { + @Override + public CallbackResponse cursorReady(AsyncResultSet resultSet) { + try { + while (true) { + switch (resultSet.tryNext()) { + case OK: + break; + case DONE: + return CallbackResponse.DONE; + case NOT_READY: + return CallbackResponse.CONTINUE; + } } + } catch (SpannerException e) { + return CallbackResponse.DONE; } - } catch (SpannerException e) { - return CallbackResponse.DONE; } - } - })); + })); + } } - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testUpdateAsyncWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public Long run(TransactionContext transaction) throws Exception { - return SpannerApiFutures.get(transaction.executeUpdateAsync(UPDATE_STATEMENT)); - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + @Test + public void testUpdateAsyncWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Long run(TransactionContext transaction) throws Exception { + return SpannerApiFutures.get(transaction.executeUpdateAsync(UPDATE_STATEMENT)); + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); - } - @Test - public void testBatchUpdateAsyncWithInlineBeginDidNotReturnTransaction() { - DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); - // This will cause the first statement that requests a transaction to not return a transaction - // id. - mockSpanner.ignoreNextInlineBeginRequest(); - try { - client - .readWriteTransaction() - .run( - new TransactionCallable() { - @Override - public long[] run(TransactionContext transaction) throws Exception { - return SpannerApiFutures.get( - transaction.batchUpdateAsync(Arrays.asList(UPDATE_STATEMENT))); - } - }); - fail("missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + @Test + public void testBatchUpdateAsyncWithInlineBeginDidNotReturnTransaction() { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // This will cause the first statement that requests a transaction to not return a transaction + // id. + mockSpanner.ignoreNextInlineBeginRequest(); + try { + client + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public long[] run(TransactionContext transaction) throws Exception { + return SpannerApiFutures.get( + transaction.batchUpdateAsync(Arrays.asList(UPDATE_STATEMENT))); + } + }); + fail("missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); + assertThat(e.getMessage()).contains(AbstractReadContext.NO_TRANSACTION_RETURNED_MSG); + } + assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); + assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); + assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0); - assertThat(countRequests(ExecuteBatchDmlRequest.class)).isEqualTo(1); - assertThat(countRequests(CommitRequest.class)).isEqualTo(0); } - private int countRequests(Class requestType) { + private static int countRequests(Class requestType) { int count = 0; for (AbstractMessage msg : mockSpanner.getRequests()) { if (msg.getClass().equals(requestType)) { @@ -1769,7 +1796,7 @@ private int countRequests(Class requestType) { return count; } - private int countTransactionsStarted() { + private static int countTransactionsStarted() { return mockSpanner.getTransactionsStarted().size(); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java index 5f3063bc7b2..34754e72719 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java @@ -96,7 +96,7 @@ public synchronized Throwable getCause() { private static Exception createDelayedInternal() { return new DelayedStatusRuntimeException( io.grpc.Status.INTERNAL.withDescription("Delayed test exception.").asRuntimeException(), - 500L); + 1000L); } public static enum ExceptionType { @@ -224,12 +224,13 @@ public void setUp() throws Exception { RetrySettings retrySettingsWithLowTimeout = RetrySettings.newBuilder() .setInitialRetryDelay(Duration.ofMillis(1L)) - .setMaxRetryDelay(Duration.ofMillis(1000L)) + .setMaxRetryDelay(Duration.ofMillis(10L)) .setInitialRpcTimeout(Duration.ofMillis(20L)) .setMaxRpcTimeout(Duration.ofMillis(200L)) - .setRetryDelayMultiplier(1000.0d) + .setRetryDelayMultiplier(1.3d) .setMaxAttempts(10) .setTotalTimeout(Duration.ofMillis(20000L)) + .setJittered(false) .build(); RetrySettings retrySettingsWithHighTimeout = RetrySettings.newBuilder() @@ -453,6 +454,19 @@ public void getInstanceTest() { @Test public void createInstanceTest() throws Exception { + boolean methodIsIdempotent = + !spanner + .getOptions() + .getInstanceAdminStubSettings() + .createInstanceOperationSettings() + .getInitialCallSettings() + .getRetryableCodes() + .isEmpty(); + if (!methodIsIdempotent && exceptionType == ExceptionType.DELAYED) { + // Skip this test as the method is non-idempotent and won't retry anyways. + return; + } + Exception exception = setupException(); InstanceName name = InstanceName.of(PROJECT, "INSTANCE"); InstanceConfigName config = InstanceConfigName.of(PROJECT, "INSTANCE_CONFIG"); @@ -480,14 +494,6 @@ public void createInstanceTest() throws Exception { } mockInstanceAdmin.addResponse(resultOperation); - boolean methodIsIdempotent = - !spanner - .getOptions() - .getInstanceAdminStubSettings() - .createInstanceOperationSettings() - .getInitialCallSettings() - .getRetryableCodes() - .isEmpty(); for (int i = 0; i < 2; i++) { OperationFuture actualResponse = client.createInstance( @@ -518,6 +524,19 @@ public void createInstanceTest() throws Exception { @Test public void updateInstanceTest() throws Exception { + boolean methodIsIdempotent = + !spanner + .getOptions() + .getInstanceAdminStubSettings() + .updateInstanceOperationSettings() + .getInitialCallSettings() + .getRetryableCodes() + .isEmpty(); + if (!methodIsIdempotent && exceptionType == ExceptionType.DELAYED) { + // Skip this test as the method is non-idempotent and won't retry anyways. + return; + } + Exception exception = setupException(); InstanceName name = InstanceName.of(PROJECT, "INSTANCE"); InstanceConfigName config = InstanceConfigName.of(PROJECT, "INSTANCE_CONFIG"); @@ -545,14 +564,6 @@ public void updateInstanceTest() throws Exception { } mockInstanceAdmin.addResponse(resultOperation); - boolean methodIsIdempotent = - !spanner - .getOptions() - .getInstanceAdminStubSettings() - .updateInstanceOperationSettings() - .getInitialCallSettings() - .getRetryableCodes() - .isEmpty(); for (int i = 0; i < 2; i++) { OperationFuture actualResponse = client.updateInstance( 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 5dc442a62e3..424e9796923 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 @@ -1809,8 +1809,16 @@ public void commit(CommitRequest request, StreamObserver respons } simulateAbort(session, request.getTransactionId()); commitTransaction(transaction.getId()); - responseObserver.onNext( - CommitResponse.newBuilder().setCommitTimestamp(getCurrentGoogleTimestamp()).build()); + CommitResponse.Builder responseBuilder = + CommitResponse.newBuilder().setCommitTimestamp(getCurrentGoogleTimestamp()); + if (request.getReturnCommitStats()) { + responseBuilder.setCommitStats( + com.google.spanner.v1.CommitResponse.CommitStats.newBuilder() + // This is not really always equal, but at least it returns a value. + .setMutationCount(request.getMutationsCount()) + .build()); + } + responseObserver.onNext(responseBuilder.build()); responseObserver.onCompleted(); } catch (StatusRuntimeException t) { responseObserver.onError(t); @@ -1966,7 +1974,7 @@ public void waitForLastRequestToBe(Class type, long t Stopwatch watch = Stopwatch.createStarted(); while (!(this.requests.peekLast() != null && this.requests.peekLast().getClass().equals(type))) { - Thread.sleep(10L); + Thread.sleep(1L); if (watch.elapsed(TimeUnit.MILLISECONDS) > timeoutMillis) { throw new TimeoutException( "Timeout while waiting for last request to become " + type.getName()); @@ -1982,7 +1990,7 @@ public void waitForRequestsToContain(Class type, long throws InterruptedException, TimeoutException { Stopwatch watch = Stopwatch.createStarted(); while (countRequestsOfType(type) == 0) { - Thread.sleep(10L); + Thread.sleep(1L); if (watch.elapsed(TimeUnit.MILLISECONDS) > timeoutMillis) { throw new TimeoutException( "Timeout while waiting for requests to contain " + type.getName()); @@ -1999,7 +2007,7 @@ public void waitForRequestsToContain( if (msg.iterator().hasNext()) { break; } - Thread.sleep(10L); + Thread.sleep(1L); if (watch.elapsed(TimeUnit.MILLISECONDS) > timeoutMillis) { throw new TimeoutException( "Timeout while waiting for requests to contain the wanted request"); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OptionsTest.java index 5a2777570b7..db642aedd12 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OptionsTest.java @@ -17,6 +17,8 @@ package com.google.cloud.spanner; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -221,22 +223,57 @@ public void queryEquality() { } @Test - public void testFromTransactionOptions() { + public void testFromTransactionOptions_toStringNoOptions() { Options opts = Options.fromTransactionOptions(); assertThat(opts.toString()).isEqualTo(""); } @Test - public void testTransactionOptionsEquality() { - Options o1; - Options o2; + public void testFromTransactionOptions_toStringWithCommitStats() { + Options options = Options.fromTransactionOptions(Options.commitStats()); + assertThat(options.toString()).contains("withCommitStats: true"); + } - o1 = Options.fromTransactionOptions(); - o2 = Options.fromTransactionOptions(); - assertThat(o1.equals(o2)).isTrue(); + @Test + public void testTransactionOptions_noOptionsAreEqual() { + Options option1 = Options.fromTransactionOptions(); + Options option2 = Options.fromTransactionOptions(); + assertEquals(option1, option2); + } - o2 = Options.fromReadOptions(Options.prefetchChunks(1)); - assertThat(o1.equals(o2)).isFalse(); + @Test + public void testTransactionOptions_withCommitStatsAreEqual() { + Options option1 = Options.fromTransactionOptions(Options.commitStats()); + Options option2 = Options.fromTransactionOptions(Options.commitStats()); + assertEquals(option1, option2); + } + + @Test + public void testTransactionOptions_withCommitStatsAndOtherOptionAreNotEqual() { + Options option1 = Options.fromTransactionOptions(Options.commitStats()); + Options option2 = Options.fromQueryOptions(Options.prefetchChunks(10)); + assertNotEquals(option1, option2); + } + + @Test + public void testTransactionOptions_noOptionsHashCode() { + Options option1 = Options.fromTransactionOptions(); + Options option2 = Options.fromTransactionOptions(); + assertEquals(option2.hashCode(), option1.hashCode()); + } + + @Test + public void testTransactionOptions_withCommitStatsHashCode() { + Options option1 = Options.fromTransactionOptions(Options.commitStats()); + Options option2 = Options.fromTransactionOptions(Options.commitStats()); + assertEquals(option2.hashCode(), option1.hashCode()); + } + + @Test + public void testTransactionOptions_withCommitStatsAndOtherOptionHashCode() { + Options option1 = Options.fromTransactionOptions(Options.commitStats()); + Options option2 = Options.fromQueryOptions(Options.prefetchChunks(10)); + assertNotEquals(option2.hashCode(), option1.hashCode()); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java index e49c7e739cc..157d71bf367 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java @@ -18,6 +18,10 @@ import static com.google.cloud.spanner.SpannerApiFutures.get; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.core.ApiFunction; @@ -1151,7 +1155,7 @@ public void transactionManagerReadRowUsingIndex() throws InterruptedException { @Test public void transactionManagerUpdate() throws InterruptedException { invalidateSessionPool(); - try (TransactionManager manager = client.transactionManager()) { + try (TransactionManager manager = client.transactionManager(Options.commitStats())) { long count; TransactionContext transaction = manager.begin(); while (true) { @@ -1164,10 +1168,11 @@ public void transactionManagerUpdate() throws InterruptedException { transaction = manager.resetForRetry(); } } - assertThat(count).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); + assertEquals(UPDATE_COUNT, count); + assertNotNull(manager.getCommitResponse().getCommitStats()); + assertFalse(failOnInvalidatedSession); } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertTrue(failOnInvalidatedSession); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolStressTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolStressTest.java index a3b2a3c5425..2f0cd239abd 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolStressTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolStressTest.java @@ -62,9 +62,6 @@ public class SessionPoolStressTest extends BaseSessionPoolTest { @Parameter(0) - public double writeSessionsFraction; - - @Parameter(1) public boolean shouldBlock; DatabaseId db = DatabaseId.of("projects/p/instances/i/databases/unused"); @@ -85,13 +82,11 @@ public class SessionPoolStressTest extends BaseSessionPoolTest { int minSessionsWhenSessionClosed = Integer.MAX_VALUE; Exception e; - @Parameters(name = "write fraction = {0}, should block = {1}") + @Parameters(name = "should block = {0}") public static Collection data() { List params = new ArrayList<>(); - for (double writeFraction = 0; writeFraction <= 1; writeFraction += 0.5) { - params.add(new Object[] {writeFraction, true}); - params.add(new Object[] {writeFraction, false}); - } + params.add(new Object[] {true}); + params.add(new Object[] {false}); return params; } @@ -236,12 +231,8 @@ public void stressTest() throws Exception { setupSpanner(db); int minSessions = 2; int maxSessions = concurrentThreads / 2; - float writeSessionsFraction = 0.5f; SessionPoolOptions.Builder builder = - SessionPoolOptions.newBuilder() - .setMinSessions(minSessions) - .setMaxSessions(maxSessions) - .setWriteSessionsFraction(writeSessionsFraction); + SessionPoolOptions.newBuilder().setMinSessions(minSessions).setMaxSessions(maxSessions); if (shouldBlock) { builder.setBlockIfPoolExhausted(); } else { @@ -273,7 +264,7 @@ public void run() { PooledSessionFuture session = pool.getSession(); session.get(); Uninterruptibles.sleepUninterruptibly( - random.nextInt(5), TimeUnit.MILLISECONDS); + random.nextInt(2), TimeUnit.MILLISECONDS); resetTransaction(session.get().delegate); session.close(); } catch (SpannerException e) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java index a163b829140..337510757d8 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java @@ -676,7 +676,7 @@ public void run() { clock.currentTimeMillis += clock.currentTimeMillis + (options.getKeepAliveIntervalMinutes() + 5) * 60 * 1000; session1 = pool.getSession(); - session1.writeAtLeastOnce(new ArrayList()); + session1.writeAtLeastOnceWithOptions(new ArrayList()); session1.close(); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numKeepAliveCycles); // The session pool only keeps MinSessions + MaxIdleSessions alive. @@ -900,8 +900,7 @@ public void testSessionNotFoundReadWriteTransaction() { when(closedSession.newTransaction(Options.fromTransactionOptions())) .thenReturn(closedTransactionContext); when(closedSession.beginTransactionAsync()).thenThrow(sessionNotFound); - TransactionRunnerImpl closedTransactionRunner = - new TransactionRunnerImpl(closedSession, rpc, 10); + TransactionRunnerImpl closedTransactionRunner = new TransactionRunnerImpl(closedSession); closedTransactionRunner.setSpan(mock(Span.class)); when(closedSession.readWriteTransaction()).thenReturn(closedTransactionRunner); @@ -915,8 +914,7 @@ public void testSessionNotFoundReadWriteTransaction() { .thenReturn(openTransactionContext); when(openSession.beginTransactionAsync()) .thenReturn(ApiFutures.immediateFuture(ByteString.copyFromUtf8("open-txn"))); - TransactionRunnerImpl openTransactionRunner = - new TransactionRunnerImpl(openSession, mock(SpannerRpc.class), 10); + TransactionRunnerImpl openTransactionRunner = new TransactionRunnerImpl(openSession); openTransactionRunner.setSpan(mock(Span.class)); when(openSession.readWriteTransaction()).thenReturn(openTransactionRunner); @@ -1047,10 +1045,13 @@ public void testSessionNotFoundWrite() { SpannerExceptionFactoryTest.newSessionNotFoundException(sessionName); List mutations = Arrays.asList(Mutation.newInsertBuilder("FOO").build()); final SessionImpl closedSession = mockSession(); - when(closedSession.write(mutations)).thenThrow(sessionNotFound); + when(closedSession.writeWithOptions(mutations)).thenThrow(sessionNotFound); final SessionImpl openSession = mockSession(); - when(openSession.write(mutations)).thenReturn(Timestamp.now()); + com.google.cloud.spanner.CommitResponse response = + mock(com.google.cloud.spanner.CommitResponse.class); + when(response.getCommitTimestamp()).thenReturn(Timestamp.now()); + when(openSession.writeWithOptions(mutations)).thenReturn(response); doAnswer( new Answer() { @Override @@ -1099,10 +1100,13 @@ public void testSessionNotFoundWriteAtLeastOnce() { SpannerExceptionFactoryTest.newSessionNotFoundException(sessionName); List mutations = Arrays.asList(Mutation.newInsertBuilder("FOO").build()); final SessionImpl closedSession = mockSession(); - when(closedSession.writeAtLeastOnce(mutations)).thenThrow(sessionNotFound); + when(closedSession.writeAtLeastOnceWithOptions(mutations)).thenThrow(sessionNotFound); final SessionImpl openSession = mockSession(); - when(openSession.writeAtLeastOnce(mutations)).thenReturn(Timestamp.now()); + com.google.cloud.spanner.CommitResponse response = + mock(com.google.cloud.spanner.CommitResponse.class); + when(response.getCommitTimestamp()).thenReturn(Timestamp.now()); + when(openSession.writeAtLeastOnceWithOptions(mutations)).thenReturn(response); doAnswer( new Answer() { @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionContextImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionContextImplTest.java index 21f2a59fb39..1ca9e0e6673 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionContextImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionContextImplTest.java @@ -16,14 +16,19 @@ package com.google.cloud.spanner; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.api.core.ApiFutures; import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl; import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.protobuf.ByteString; import com.google.rpc.Code; import com.google.rpc.Status; +import com.google.spanner.v1.CommitRequest; import com.google.spanner.v1.ExecuteBatchDmlRequest; import com.google.spanner.v1.ExecuteBatchDmlResponse; import java.util.Arrays; @@ -45,6 +50,35 @@ public void batchDmlException() { batchDml(Code.FAILED_PRECONDITION_VALUE); } + @SuppressWarnings("unchecked") + @Test + public void testReturnCommitStats() { + SessionImpl session = mock(SessionImpl.class); + when(session.getName()).thenReturn("test"); + ByteString transactionId = ByteString.copyFromUtf8("test"); + SpannerRpc rpc = mock(SpannerRpc.class); + when(rpc.commitAsync(any(CommitRequest.class), anyMap())) + .thenReturn( + ApiFutures.immediateFuture(com.google.spanner.v1.CommitResponse.getDefaultInstance())); + + try (TransactionContextImpl context = + TransactionContextImpl.newBuilder() + .setSession(session) + .setRpc(rpc) + .setTransactionId(transactionId) + .setOptions(Options.fromTransactionOptions(Options.commitStats())) + .build()) { + context.commitAsync(); + CommitRequest request = + CommitRequest.newBuilder() + .setReturnCommitStats(true) + .setSession(session.getName()) + .setTransactionId(transactionId) + .build(); + verify(rpc).commitAsync(Mockito.eq(request), anyMap()); + } + } + @SuppressWarnings("unchecked") private void batchDml(int status) { SessionImpl session = mock(SessionImpl.class); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java index 39e65e21d91..4ffed254cb6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerImplTest.java @@ -37,7 +37,6 @@ import com.google.protobuf.Empty; import com.google.spanner.v1.BeginTransactionRequest; import com.google.spanner.v1.CommitRequest; -import com.google.spanner.v1.CommitResponse; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.ResultSetMetadata; @@ -141,7 +140,8 @@ public void transactionRolledBackOnClose() { public void commitSucceeds() { when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); Timestamp commitTimestamp = Timestamp.ofTimeMicroseconds(1); - when(txn.commitTimestamp()).thenReturn(commitTimestamp); + CommitResponse response = new CommitResponse(commitTimestamp); + when(txn.getCommitResponse()).thenReturn(response); manager.begin(); manager.commit(); assertThat(manager.getState()).isEqualTo(TransactionState.COMMITTED); @@ -266,12 +266,12 @@ public ApiFuture answer(InvocationOnMock invocation) { }); when(rpc.commitAsync(Mockito.any(CommitRequest.class), Mockito.anyMap())) .thenAnswer( - new Answer>() { + new Answer>() { @Override - public ApiFuture answer(InvocationOnMock invocation) - throws Throwable { + public ApiFuture answer( + InvocationOnMock invocation) throws Throwable { return ApiFutures.immediateFuture( - CommitResponse.newBuilder() + com.google.spanner.v1.CommitResponse.newBuilder() .setCommitTimestamp( com.google.protobuf.Timestamp.newBuilder() .setSeconds(System.currentTimeMillis() * 1000)) @@ -360,12 +360,12 @@ public com.google.spanner.v1.ResultSet answer(InvocationOnMock invocation) }); when(rpc.commitAsync(Mockito.any(CommitRequest.class), Mockito.anyMap())) .thenAnswer( - new Answer>() { + new Answer>() { @Override - public ApiFuture answer(InvocationOnMock invocation) - throws Throwable { + public ApiFuture answer( + InvocationOnMock invocation) throws Throwable { return ApiFutures.immediateFuture( - CommitResponse.newBuilder() + com.google.spanner.v1.CommitResponse.newBuilder() .setCommitTimestamp( com.google.protobuf.Timestamp.newBuilder() .setSeconds(System.currentTimeMillis() * 1000)) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java index fc7dde22f45..b65f70b3845 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionRunnerImplTest.java @@ -123,7 +123,7 @@ public ResultSet answer(InvocationOnMock invocation) throws Throwable { return builder.build(); } }); - transactionRunner = new TransactionRunnerImpl(session, rpc, 1); + transactionRunner = new TransactionRunnerImpl(session); when(rpc.commitAsync(Mockito.any(CommitRequest.class), Mockito.anyMap())) .thenReturn( ApiFutures.immediateFuture( @@ -323,7 +323,7 @@ public void prepareReadWriteTransaction() { } }; session.setCurrentSpan(mock(Span.class)); - TransactionRunnerImpl runner = new TransactionRunnerImpl(session, rpc, 10); + TransactionRunnerImpl runner = new TransactionRunnerImpl(session); runner.setSpan(mock(Span.class)); assertThat(usedInlinedBegin).isFalse(); runner.run( @@ -356,7 +356,7 @@ private long[] batchDmlException(int status) { .thenReturn( ApiFutures.immediateFuture(ByteString.copyFromUtf8(UUID.randomUUID().toString()))); when(session.getName()).thenReturn(SessionId.of("p", "i", "d", "test").getName()); - TransactionRunnerImpl runner = new TransactionRunnerImpl(session, rpc, 10); + TransactionRunnerImpl runner = new TransactionRunnerImpl(session); runner.setSpan(mock(Span.class)); ExecuteBatchDmlResponse response1 = ExecuteBatchDmlResponse.newBuilder() diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClientTest.java index 956f66ee6cf..20a1869946e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/admin/database/v1/DatabaseAdminClientTest.java @@ -217,6 +217,8 @@ public void createDatabaseTest() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() @@ -268,6 +270,8 @@ public void createDatabaseTest2() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() @@ -319,6 +323,8 @@ public void getDatabaseTest() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); mockDatabaseAdmin.addResponse(expectedResponse); @@ -359,6 +365,8 @@ public void getDatabaseTest2() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); mockDatabaseAdmin.addResponse(expectedResponse); @@ -871,6 +879,7 @@ public void createBackupTest() throws Exception { Backup expectedResponse = Backup.newBuilder() .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) + .setVersionTime(Timestamp.newBuilder().build()) .setExpireTime(Timestamp.newBuilder().build()) .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString()) .setCreateTime(Timestamp.newBuilder().build()) @@ -928,6 +937,7 @@ public void createBackupTest2() throws Exception { Backup expectedResponse = Backup.newBuilder() .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) + .setVersionTime(Timestamp.newBuilder().build()) .setExpireTime(Timestamp.newBuilder().build()) .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString()) .setCreateTime(Timestamp.newBuilder().build()) @@ -985,6 +995,7 @@ public void getBackupTest() throws Exception { Backup expectedResponse = Backup.newBuilder() .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) + .setVersionTime(Timestamp.newBuilder().build()) .setExpireTime(Timestamp.newBuilder().build()) .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString()) .setCreateTime(Timestamp.newBuilder().build()) @@ -1028,6 +1039,7 @@ public void getBackupTest2() throws Exception { Backup expectedResponse = Backup.newBuilder() .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) + .setVersionTime(Timestamp.newBuilder().build()) .setExpireTime(Timestamp.newBuilder().build()) .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString()) .setCreateTime(Timestamp.newBuilder().build()) @@ -1071,6 +1083,7 @@ public void updateBackupTest() throws Exception { Backup expectedResponse = Backup.newBuilder() .setDatabase(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) + .setVersionTime(Timestamp.newBuilder().build()) .setExpireTime(Timestamp.newBuilder().build()) .setName(BackupName.of("[PROJECT]", "[INSTANCE]", "[BACKUP]").toString()) .setCreateTime(Timestamp.newBuilder().build()) @@ -1275,6 +1288,8 @@ public void restoreDatabaseTest() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() @@ -1329,6 +1344,8 @@ public void restoreDatabaseTest2() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() @@ -1383,6 +1400,8 @@ public void restoreDatabaseTest3() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() @@ -1437,6 +1456,8 @@ public void restoreDatabaseTest4() throws Exception { .setName(DatabaseName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]").toString()) .setCreateTime(Timestamp.newBuilder().build()) .setRestoreInfo(RestoreInfo.newBuilder().build()) + .setVersionRetentionPeriod("versionRetentionPeriod-629783929") + .setEarliestVersionTime(Timestamp.newBuilder().build()) .build(); Operation resultOperation = Operation.newBuilder() 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 b2ebd826615..112621f8533 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 @@ -16,13 +16,16 @@ package com.google.cloud.spanner.connection; +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; import com.google.cloud.spanner.connection.ITAbstractSpannerTest.AbortInterceptor; import com.google.cloud.spanner.connection.ITAbstractSpannerTest.ITConnection; +import com.google.cloud.spanner.connection.SpannerPool.CheckAndCloseSpannersMode; import com.google.common.util.concurrent.AbstractFuture; import com.google.longrunning.GetOperationRequest; import com.google.longrunning.Operation; @@ -49,6 +52,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import org.junit.After; import org.junit.AfterClass; @@ -113,6 +117,7 @@ public abstract class AbstractMockServerTest { private boolean futureParentHandlers; private boolean exceptionRunnableParentHandlers; private boolean nettyServerParentHandlers; + private boolean clientStreamParentHandlers; @BeforeClass public static void startStaticServer() throws IOException { @@ -152,9 +157,7 @@ public void getOperation( @AfterClass public static void stopServer() throws Exception { - SpannerPool.closeSpannerPool(); server.shutdown(); - server.awaitTermination(); } @Before @@ -169,22 +172,30 @@ public void setupResults() { nettyServerParentHandlers = Logger.getLogger("io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler") .getUseParentHandlers(); + clientStreamParentHandlers = + Logger.getLogger("io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler") + .getUseParentHandlers(); Logger.getLogger(AbstractFuture.class.getName()).setUseParentHandlers(false); Logger.getLogger(LogExceptionRunnable.class.getName()).setUseParentHandlers(false); Logger.getLogger("io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler") .setUseParentHandlers(false); + Logger.getLogger("io.grpc.internal.AbstractClientStream").setUseParentHandlers(false); } @After public void closeSpannerPool() { try { - SpannerPool.closeSpannerPool(); + SpannerPool.INSTANCE.checkAndCloseSpanners( + CheckAndCloseSpannersMode.ERROR, + new ForceCloseSpannerFunction(100L, TimeUnit.MILLISECONDS)); } finally { Logger.getLogger(AbstractFuture.class.getName()).setUseParentHandlers(futureParentHandlers); Logger.getLogger(LogExceptionRunnable.class.getName()) .setUseParentHandlers(exceptionRunnableParentHandlers); Logger.getLogger("io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler") .setUseParentHandlers(nettyServerParentHandlers); + Logger.getLogger("io.grpc.internal.AbstractClientStream") + .setUseParentHandlers(clientStreamParentHandlers); } } 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 88f942122a9..113738bf058 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 @@ -37,6 +37,7 @@ import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.NoCredentials; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.CommitResponse; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.ForwardingResultSet; @@ -84,7 +85,7 @@ public class ConnectionImplTest { static class SimpleTransactionManager implements TransactionManager { private TransactionState state; - private Timestamp commitTimestamp; + private CommitResponse commitResponse; private TransactionContext txContext; private SimpleTransactionManager(TransactionContext txContext) { @@ -99,7 +100,7 @@ public TransactionContext begin() { @Override public void commit() { - commitTimestamp = Timestamp.now(); + commitResponse = new CommitResponse(Timestamp.ofTimeSecondsAndNanos(1, 1)); state = TransactionState.COMMITTED; } @@ -115,7 +116,12 @@ public TransactionContext resetForRetry() { @Override public Timestamp getCommitTimestamp() { - return commitTimestamp; + return commitResponse == null ? null : commitResponse.getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + return commitResponse; } @Override @@ -317,15 +323,15 @@ public Timestamp answer(InvocationOnMock invocation) { public TransactionRunner answer(InvocationOnMock invocation) { TransactionRunner runner = new TransactionRunner() { - private Timestamp commitTimestamp; + private CommitResponse commitResponse; @Override public T run(TransactionCallable callable) { - this.commitTimestamp = Timestamp.now(); - TransactionContext tx = mock(TransactionContext.class); - when(tx.executeUpdate(Statement.of(UPDATE))).thenReturn(1L); + commitResponse = new CommitResponse(Timestamp.ofTimeSecondsAndNanos(1, 1)); + TransactionContext transaction = mock(TransactionContext.class); + when(transaction.executeUpdate(Statement.of(UPDATE))).thenReturn(1L); try { - return callable.run(tx); + return callable.run(transaction); } catch (Exception e) { throw SpannerExceptionFactory.newSpannerException(e); } @@ -333,7 +339,12 @@ public T run(TransactionCallable callable) { @Override public Timestamp getCommitTimestamp() { - return commitTimestamp; + return commitResponse == null ? null : commitResponse.getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + return commitResponse; } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java index b2f4ea086ca..46452d3fc08 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java @@ -421,4 +421,28 @@ public void testLenient() { assertThat(e.getMessage()).contains("bar"); } } + + @Test + public void testMinSessions() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?minSessions=400") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertThat(options.getMinSessions()).isEqualTo(400); + assertThat(options.getSessionPoolOptions().getMinSessions()).isEqualTo(400); + } + + @Test + public void testMaxSessions() { + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?maxSessions=4000") + .setCredentialsUrl(FILE_TEST_PATH) + .build(); + assertThat(options.getMaxSessions()).isEqualTo(4000); + assertThat(options.getSessionPoolOptions().getMaxSessions()).isEqualTo(4000); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java index de820ccbcc5..4a26721fe8f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java @@ -19,156 +19,241 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.cloud.spanner.AbortedException; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; +import com.google.protobuf.AbstractMessage; +import com.google.spanner.v1.BatchCreateSessionsRequest; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.AfterClass; import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; -public class ConnectionTest extends AbstractMockServerTest { - @Test - public void testDefaultOptimizerVersion() { - try (Connection connection = createConnection()) { - try (ResultSet rs = - connection.executeQuery(Statement.of("SHOW VARIABLE OPTIMIZER_VERSION"))) { - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("OPTIMIZER_VERSION")).isEqualTo(""); - assertThat(rs.next()).isFalse(); - } - } - } +@RunWith(Enclosed.class) +public class ConnectionTest { - @Test - public void testUseOptimizerVersionFromEnvironment() { - try { - SpannerOptions.useEnvironment( - new SpannerOptions.SpannerEnvironment() { - @Override - public String getOptimizerVersion() { - return "20"; - } - }); + public static class DefaultConnectionOptionsTest extends AbstractMockServerTest { + @Test + public void testDefaultOptimizerVersion() { try (Connection connection = createConnection()) { - // Do a query and verify that the version from the environment is used. - try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { - assertThat(rs.next()).isTrue(); - assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); - assertThat(rs.next()).isFalse(); - // Verify query options from the environment. - ExecuteSqlRequest request = getLastExecuteSqlRequest(); - assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("20"); - } - // Now set one of the query options on the connection. That option should be used in - // combination with the other option from the environment. - connection.execute(Statement.of("SET OPTIMIZER_VERSION='30'")); - try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { - assertThat(rs.next()).isTrue(); - assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); - assertThat(rs.next()).isFalse(); - - ExecuteSqlRequest request = getLastExecuteSqlRequest(); - // Optimizer version should come from the connection. - assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("30"); - } - // Now specify options directly for the query. These should override both the environment - // and what is set on the connection. try (ResultSet rs = - connection.executeQuery( - Statement.newBuilder(SELECT_COUNT_STATEMENT.getSql()) - .withQueryOptions( - QueryOptions.newBuilder() - .setOptimizerVersion("user-defined-version") - .build()) - .build())) { + connection.executeQuery(Statement.of("SHOW VARIABLE OPTIMIZER_VERSION"))) { assertThat(rs.next()).isTrue(); - assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); + assertThat(rs.getString("OPTIMIZER_VERSION")).isEqualTo(""); assertThat(rs.next()).isFalse(); - - ExecuteSqlRequest request = getLastExecuteSqlRequest(); - // Optimizer version should come from the query. - assertThat(request.getQueryOptions().getOptimizerVersion()) - .isEqualTo("user-defined-version"); } } - } finally { - SpannerOptions.useDefaultEnvironment(); } - } - @Test - public void testExecuteInvalidBatchUpdate() { - try (Connection connection = createConnection()) { + @Test + public void testUseOptimizerVersionFromEnvironment() { try { - connection.executeBatchUpdate(ImmutableList.of(INSERT_STATEMENT, SELECT_RANDOM_STATEMENT)); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + SpannerOptions.useEnvironment( + new SpannerOptions.SpannerEnvironment() { + @Override + public String getOptimizerVersion() { + return "20"; + } + }); + try (Connection connection = createConnection()) { + // Do a query and verify that the version from the environment is used. + try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); + assertThat(rs.next()).isFalse(); + // Verify query options from the environment. + ExecuteSqlRequest request = getLastExecuteSqlRequest(); + assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("20"); + } + // Now set one of the query options on the connection. That option should be used in + // combination with the other option from the environment. + connection.execute(Statement.of("SET OPTIMIZER_VERSION='30'")); + try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); + assertThat(rs.next()).isFalse(); + + ExecuteSqlRequest request = getLastExecuteSqlRequest(); + // Optimizer version should come from the connection. + assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("30"); + } + // Now specify options directly for the query. These should override both the environment + // and what is set on the connection. + try (ResultSet rs = + connection.executeQuery( + Statement.newBuilder(SELECT_COUNT_STATEMENT.getSql()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("user-defined-version") + .build()) + .build())) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); + assertThat(rs.next()).isFalse(); + + ExecuteSqlRequest request = getLastExecuteSqlRequest(); + // Optimizer version should come from the query. + assertThat(request.getQueryOptions().getOptimizerVersion()) + .isEqualTo("user-defined-version"); + } + } + } finally { + SpannerOptions.useDefaultEnvironment(); } } - } - @Test - public void testQueryAborted() { - try (Connection connection = createConnection()) { - connection.setRetryAbortsInternally(false); - for (boolean abort : new Boolean[] {true, false}) { + @Test + public void testExecuteInvalidBatchUpdate() { + try (Connection connection = createConnection()) { try { - if (abort) { - mockSpanner.abortNextStatement(); + connection.executeBatchUpdate( + ImmutableList.of(INSERT_STATEMENT, SELECT_RANDOM_STATEMENT)); + fail("Missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + } + } + + @Test + public void testQueryAborted() { + try (Connection connection = createConnection()) { + connection.setRetryAbortsInternally(false); + for (boolean abort : new Boolean[] {true, false}) { + try { + if (abort) { + mockSpanner.abortNextStatement(); + } + connection.executeQuery(SELECT_RANDOM_STATEMENT); + assertThat(abort).isFalse(); + connection.commit(); + } catch (AbortedException e) { + assertThat(abort).isTrue(); + connection.rollback(); } - connection.executeQuery(SELECT_RANDOM_STATEMENT); - assertThat(abort).isFalse(); - connection.commit(); - } catch (AbortedException e) { - assertThat(abort).isTrue(); - connection.rollback(); } } } - } - @Test - public void testUpdateAborted() { - try (Connection connection = createConnection()) { - connection.setRetryAbortsInternally(false); - for (boolean abort : new Boolean[] {true, false}) { - try { - if (abort) { - mockSpanner.abortNextStatement(); + @Test + public void testUpdateAborted() { + try (Connection connection = createConnection()) { + connection.setRetryAbortsInternally(false); + for (boolean abort : new Boolean[] {true, false}) { + try { + if (abort) { + mockSpanner.abortNextStatement(); + } + connection.executeUpdate(INSERT_STATEMENT); + assertThat(abort).isFalse(); + connection.commit(); + } catch (AbortedException e) { + assertThat(abort).isTrue(); + connection.rollback(); } - connection.executeUpdate(INSERT_STATEMENT); - assertThat(abort).isFalse(); - connection.commit(); - } catch (AbortedException e) { - assertThat(abort).isTrue(); - connection.rollback(); } } } - } - @Test - public void testBatchUpdateAborted() { - try (Connection connection = createConnection()) { - connection.setRetryAbortsInternally(false); - for (boolean abort : new Boolean[] {true, false}) { - try { - if (abort) { - mockSpanner.abortNextStatement(); + @Test + public void testBatchUpdateAborted() { + try (Connection connection = createConnection()) { + connection.setRetryAbortsInternally(false); + for (boolean abort : new Boolean[] {true, false}) { + try { + if (abort) { + mockSpanner.abortNextStatement(); + } + connection.executeBatchUpdate(ImmutableList.of(INSERT_STATEMENT, INSERT_STATEMENT)); + assertThat(abort).isFalse(); + connection.commit(); + } catch (AbortedException e) { + assertThat(abort).isTrue(); + connection.rollback(); } - connection.executeBatchUpdate(ImmutableList.of(INSERT_STATEMENT, INSERT_STATEMENT)); - assertThat(abort).isFalse(); - connection.commit(); - } catch (AbortedException e) { - assertThat(abort).isTrue(); - connection.rollback(); } } } } + + public static class ConnectionMinSessionsTest extends AbstractMockServerTest { + + @AfterClass + public static void reset() { + mockSpanner.reset(); + } + + protected String getBaseUrl() { + return super.getBaseUrl() + ";minSessions=1"; + } + + @Test + public void testMinSessions() throws InterruptedException, TimeoutException { + try (Connection connection = createConnection()) { + mockSpanner.waitForRequestsToContain( + new Predicate() { + @Override + public boolean apply(AbstractMessage input) { + return input instanceof BatchCreateSessionsRequest + && ((BatchCreateSessionsRequest) input).getSessionCount() == 1; + } + }, + 5000L); + } + } + } + + public static class ConnectionMaxSessionsTest extends AbstractMockServerTest { + + @AfterClass + public static void reset() { + mockSpanner.reset(); + } + + protected String getBaseUrl() { + return super.getBaseUrl() + ";maxSessions=1"; + } + + @Test + public void testMaxSessions() + throws InterruptedException, TimeoutException, ExecutionException { + try (Connection connection1 = createConnection(); + Connection connection2 = createConnection()) { + connection1.beginTransactionAsync(); + connection2.beginTransactionAsync(); + + ApiFuture count1 = connection1.executeUpdateAsync(INSERT_STATEMENT); + ApiFuture count2 = connection2.executeUpdateAsync(INSERT_STATEMENT); + + // Commit the transactions. Both should be able to finish, but both used the same session. + ApiFuture commit1 = connection1.commitAsync(); + ApiFuture commit2 = connection2.commitAsync(); + + // At least one transaction must wait until the other has finished before it can get a + // session. + assertThat(count1.isDone() && count2.isDone()).isFalse(); + assertThat(commit1.isDone() && commit2.isDone()).isFalse(); + + // Wait until both finishes. + ApiFutures.allAsList(Arrays.asList(commit1, commit2)).get(5L, TimeUnit.SECONDS); + + assertThat(count1.isDone()).isTrue(); + assertThat(count2.isDone()).isTrue(); + } + assertThat(mockSpanner.numSessionsCreated()).isEqualTo(1); + } + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java index 1e094eaeb6f..de5c9fbdeba 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java @@ -32,6 +32,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.spanner.AbortedException; +import com.google.cloud.spanner.CommitResponse; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode; @@ -69,7 +70,7 @@ private enum CommitBehavior { private static class SimpleTransactionManager implements TransactionManager { private TransactionState state; - private Timestamp commitTimestamp; + private CommitResponse commitResponse; private TransactionContext txContext; private CommitBehavior commitBehavior; @@ -88,7 +89,7 @@ public TransactionContext begin() { public void commit() { switch (commitBehavior) { case SUCCEED: - commitTimestamp = Timestamp.now(); + commitResponse = new CommitResponse(Timestamp.ofTimeSecondsAndNanos(1, 1)); state = TransactionState.COMMITTED; break; case FAIL: @@ -115,7 +116,11 @@ public TransactionContext resetForRetry() { @Override public Timestamp getCommitTimestamp() { - return commitTimestamp; + return commitResponse == null ? null : commitResponse.getCommitTimestamp(); + } + + public CommitResponse getCommitResponse() { + return commitResponse; } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java index 76ef62a21a1..af761d4cbef 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java @@ -29,6 +29,7 @@ import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.Timestamp; import com.google.cloud.spanner.AsyncResultSet; +import com.google.cloud.spanner.CommitResponse; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Key; @@ -97,7 +98,7 @@ static StatementTimeout timeout(long timeout, TimeUnit unit) { private static class SimpleTransactionManager implements TransactionManager { private TransactionState state; - private Timestamp commitTimestamp; + private CommitResponse commitResponse; private TransactionContext txContext; private CommitBehavior commitBehavior; @@ -116,7 +117,7 @@ public TransactionContext begin() { public void commit() { switch (commitBehavior) { case SUCCEED: - commitTimestamp = Timestamp.now(); + commitResponse = new CommitResponse(Timestamp.ofTimeSecondsAndNanos(1, 1)); state = TransactionState.COMMITTED; break; case FAIL: @@ -143,7 +144,12 @@ public TransactionContext resetForRetry() { @Override public Timestamp getCommitTimestamp() { - return commitTimestamp; + return commitResponse.getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + return commitResponse; } @Override @@ -387,7 +393,7 @@ public Long answer(InvocationOnMock invocation) throws Throwable { public TransactionRunner answer(InvocationOnMock invocation) { TransactionRunner runner = new TransactionRunner() { - private Timestamp commitTimestamp; + private CommitResponse commitResponse; @Override public T run(TransactionCallable callable) { @@ -398,7 +404,8 @@ public T run(TransactionCallable callable) { } catch (Exception e) { throw SpannerExceptionFactory.newSpannerException(e); } - this.commitTimestamp = Timestamp.now(); + commitResponse = + new CommitResponse(Timestamp.ofTimeSecondsAndNanos(1, 1)); return res; } else if (commitBehavior == CommitBehavior.FAIL) { throw SpannerExceptionFactory.newSpannerException( @@ -411,10 +418,17 @@ public T run(TransactionCallable callable) { @Override public Timestamp getCommitTimestamp() { - if (commitTimestamp == null) { + if (commitResponse == null) { throw new IllegalStateException("no commit timestamp"); } - return commitTimestamp; + return commitResponse.getCommitTimestamp(); + } + + public CommitResponse getCommitResponse() { + if (commitResponse == null) { + throw new IllegalStateException("no commit response"); + } + return commitResponse; } @Override 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 afc0512b4ec..4e1238077e2 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 @@ -16,16 +16,16 @@ package com.google.cloud.spanner.connection; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.auth.Credentials; import com.google.cloud.NoCredentials; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.SessionPoolOptions; @@ -33,6 +33,7 @@ import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.connection.ConnectionImpl.LeakedConnectionException; import com.google.cloud.spanner.connection.SpannerPool.CheckAndCloseSpannersMode; +import com.google.cloud.spanner.connection.SpannerPool.SpannerPoolKey; import com.google.common.base.Ticker; import com.google.common.testing.FakeTicker; import java.io.ByteArrayOutputStream; @@ -108,40 +109,40 @@ public void testGetSpanner() { // assert equal spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options1, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); spanner1 = pool.getSpanner(options2, connection1); spanner2 = pool.getSpanner(options2, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); spanner1 = pool.getSpanner(options3, connection1); spanner2 = pool.getSpanner(options3, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); spanner1 = pool.getSpanner(options4, connection1); spanner2 = pool.getSpanner(options4, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // Options 5 and 6 both use default credentials. spanner1 = pool.getSpanner(options5, connection1); spanner2 = pool.getSpanner(options6, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // assert not equal spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options2, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options3, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options4, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); spanner1 = pool.getSpanner(options2, connection1); spanner2 = pool.getSpanner(options3, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); spanner1 = pool.getSpanner(options2, connection1); spanner2 = pool.getSpanner(options4, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); spanner1 = pool.getSpanner(options3, connection1); spanner2 = pool.getSpanner(options4, connection2); - assertThat(spanner1, not(equalTo(spanner2))); + assertThat(spanner1).isNotEqualTo(spanner2); } @Test @@ -153,17 +154,17 @@ public void testRemoveConnection() { // assert equal spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options1, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // one connection removed, assert that we would still get the same Spanner pool.removeConnection(options1, connection1); spanner1 = pool.getSpanner(options1, connection1); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // remove two connections, assert that we would still get the same Spanner, as Spanners are not // directly closed and removed. pool.removeConnection(options1, connection1); pool.removeConnection(options1, connection2); spanner1 = pool.getSpanner(options1, connection1); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // remove the last connection again pool.removeConnection(options1, connection1); } @@ -171,6 +172,7 @@ public void testRemoveConnection() { private static Logger log = Logger.getLogger(SpannerPool.class.getName()); private static OutputStream logCapturingStream; private static StreamHandler customLogHandler; + private static boolean useParentHandlers; private void attachLogCapturer() { logCapturingStream = new ByteArrayOutputStream(); @@ -184,14 +186,23 @@ private void attachLogCapturer() { throw new IllegalStateException("no handlers found for logger"); } customLogHandler = new StreamHandler(logCapturingStream, handlers[0].getFormatter()); + useParentHandlers = log.getUseParentHandlers(); + log.setUseParentHandlers(false); log.addHandler(customLogHandler); } - public String getTestCapturedLog() { + private String getTestCapturedLog() { customLogHandler.flush(); return logCapturingStream.toString(); } + @AfterClass + public static void resetUseParentHandlers() { + if (useParentHandlers) { + log.setUseParentHandlers(true); + } + } + @Test public void testRemoveConnectionOptionsNotRegistered() { attachLogCapturer(); @@ -200,7 +211,7 @@ public void testRemoveConnectionOptionsNotRegistered() { pool.getSpanner(options1, connection1); pool.removeConnection(options2, connection1); String capturedLog = getTestCapturedLog(); - assertThat(capturedLog.contains(expectedLogPart), is(true)); + assertThat(capturedLog.contains(expectedLogPart)).isTrue(); } @Test @@ -211,7 +222,7 @@ public void testRemoveConnectionConnectionNotRegistered() { pool.getSpanner(options1, connection1); pool.removeConnection(options1, connection2); String capturedLog = getTestCapturedLog(); - assertThat(capturedLog.contains(expectedLogPart), is(true)); + assertThat(capturedLog.contains(expectedLogPart)).isTrue(); } @Test @@ -223,7 +234,7 @@ public void testRemoveConnectionConnectionAlreadyRemoved() { pool.removeConnection(options1, connection1); pool.removeConnection(options1, connection1); String capturedLog = getTestCapturedLog(); - assertThat(capturedLog.contains(expectedLogPart), is(true)); + assertThat(capturedLog.contains(expectedLogPart)).isTrue(); } @Test @@ -237,7 +248,7 @@ public void testCloseSpanner() { } catch (SpannerException e) { exception = e.getErrorCode() == ErrorCode.FAILED_PRECONDITION; } - assertThat(exception, is(true)); + assertThat(exception).isTrue(); // remove the connection and verify that it is possible to close pool.removeConnection(options1, connection1); @@ -249,7 +260,7 @@ public void testCloseSpanner() { Spanner spanner2 = pool.getSpanner(options1, connection1); pool.checkAndCloseSpanners(CheckAndCloseSpannersMode.WARN); String capturedLog = getTestCapturedLog(); - assertThat(capturedLog.contains(expectedLogPart), is(true)); + assertThat(capturedLog.contains(expectedLogPart)).isTrue(); verify(spanner2, never()).close(); // remove the connection and verify that it is possible to close @@ -273,11 +284,11 @@ public void testLeakedConnection() { ConnectionOptions.closeSpanner(); fail("missing expected exception"); } catch (SpannerException e) { - assertThat(e.getErrorCode(), is(equalTo(ErrorCode.FAILED_PRECONDITION))); + assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); } String capturedLog = getTestCapturedLog(); - assertThat(capturedLog.contains(LeakedConnectionException.class.getName()), is(true)); - assertThat(capturedLog.contains("testLeakedConnection"), is(true)); + assertThat(capturedLog.contains(LeakedConnectionException.class.getName())).isTrue(); + assertThat(capturedLog.contains("testLeakedConnection")).isTrue(); // Now close the connection to avoid trouble with other test cases. connection.close(); } @@ -292,7 +303,7 @@ public void testCloseUnusedSpanners() { // create two connections that use the same Spanner spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options1, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // all spanners are in use, this should have no effect pool.closeUnusedSpanners(-1L); @@ -312,8 +323,8 @@ public void testCloseUnusedSpanners() { spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options2, connection2); spanner3 = pool.getSpanner(options2, connection3); - assertThat(spanner1, not(equalTo(spanner2))); - assertThat(spanner2, is(equalTo(spanner3))); + assertThat(spanner1).isNotEqualTo(spanner2); + assertThat(spanner2).isEqualTo(spanner3); // all spanners are in use, this should have no effect pool.closeUnusedSpanners(-1L); @@ -359,7 +370,7 @@ public void testAutomaticCloser() throws InterruptedException { // create two connections that use the same Spanner spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options1, connection2); - assertThat(spanner1, is(equalTo(spanner2))); + assertThat(spanner1).isEqualTo(spanner2); // all spanners are in use, this should have no effect ticker.advance(TEST_AUTOMATIC_CLOSE_TIMEOUT_NANOS + MILLISECOND); @@ -382,8 +393,8 @@ public void testAutomaticCloser() throws InterruptedException { spanner1 = pool.getSpanner(options1, connection1); spanner2 = pool.getSpanner(options2, connection2); spanner3 = pool.getSpanner(options2, connection3); - assertThat(spanner1, not(equalTo(spanner2))); - assertThat(spanner2, is(equalTo(spanner3))); + assertThat(spanner1).isNotEqualTo(spanner2); + assertThat(spanner2).isEqualTo(spanner3); // all spanners are in use, this should have no effect ticker.advance(TEST_AUTOMATIC_CLOSE_TIMEOUT_NANOS + MILLISECOND); @@ -416,4 +427,37 @@ public void testAutomaticCloser() throws InterruptedException { verify(spanner2).close(); verify(spanner3).close(); } + + @Test + public void testSpannerPoolKeyEquality() { + ConnectionOptions options1 = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner://localhost:9010/projects/p1/instances/i/databases/d" + + "?minSessions=200;maxSessions=400;numChannels=8;usePlainText=true;userAgent=test-agent") + .setCredentials(mock(Credentials.class)) + .build(); + // options2 equals the default session pool options, and is therefore equal to ConnectionOptions + // without any session pool configuration. + ConnectionOptions options2 = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?minSessions=100;maxSessions=400") + .setCredentials(NoCredentials.getInstance()) + .build(); + ConnectionOptions options3 = + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/p/instances/i/databases/d") + .setCredentials(NoCredentials.getInstance()) + .build(); + + SpannerPoolKey key1 = SpannerPoolKey.of(options1); + SpannerPoolKey key2 = SpannerPoolKey.of(options2); + SpannerPoolKey key3 = SpannerPoolKey.of(options3); + + assertFalse(key1.equals(key2)); + assertTrue(key2.equals(key3)); + assertFalse(key1.equals(key3)); + assertFalse(key1.equals(new Object())); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncAPITest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncAPITest.java index 721536cb6b3..5bdb685c8bd 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncAPITest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITAsyncAPITest.java @@ -16,15 +16,25 @@ package com.google.cloud.spanner.it; +import static com.google.cloud.spanner.SpannerApiFutures.get; +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.cloud.spanner.AbortedException; import com.google.cloud.spanner.AsyncResultSet; import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; import com.google.cloud.spanner.AsyncResultSet.ReadyCallback; import com.google.cloud.spanner.AsyncRunner; import com.google.cloud.spanner.AsyncRunner.AsyncWork; +import com.google.cloud.spanner.AsyncTransactionManager; +import com.google.cloud.spanner.AsyncTransactionManager.AsyncTransactionFunction; +import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; @@ -35,6 +45,7 @@ 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.SpannerException; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Struct; @@ -51,6 +62,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -86,7 +98,17 @@ public static void setUpDatabase() { "CREATE INDEX TestTableByValue ON TestTable(StringValue)", "CREATE INDEX TestTableByValueDesc ON TestTable(StringValue DESC)"); client = env.getTestHelper().getDatabaseClient(db); + executor = Executors.newSingleThreadExecutor(); + } + @AfterClass + public static void cleanup() { + executor.shutdown(); + } + + @Before + public void setupData() { + client.write(Arrays.asList(Mutation.delete(TABLE_NAME, KeySet.all()))); // Includes k0..k14. Note that strings k{10,14} sort between k1 and k2. List mutations = new ArrayList<>(); for (int i = 0; i < 15; ++i) { @@ -99,12 +121,6 @@ public static void setUpDatabase() { .build()); } client.write(mutations); - executor = Executors.newSingleThreadExecutor(); - } - - @AfterClass - public static void cleanup() { - executor.shutdown(); } @Test @@ -302,4 +318,65 @@ public ApiFuture doWorkAsync(TransactionContext txn) { assertThat(client.singleUse().readRow("TestTable", Key.of("k999"), ALL_COLUMNS)).isNull(); } } + + @Test + public void testAsyncRunnerReturnsCommitStats() { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + AsyncRunner runner = client.runAsync(Options.commitStats()); + runner.runAsync( + new AsyncWork() { + @Override + public ApiFuture doWorkAsync(TransactionContext transaction) { + transaction.buffer( + Mutation.newInsertOrUpdateBuilder(TABLE_NAME) + .set("Key") + .to("k_commit_stats") + .set("StringValue") + .to("Should return commit stats") + .build()); + return ApiFutures.immediateFuture(null); + } + }, + executor); + assertNotNull(get(runner.getCommitResponse()).getCommitStats()); + // MutationCount = 2 columns + 2 secondary indexes. + assertEquals(4L, get(runner.getCommitResponse()).getCommitStats().getMutationCount()); + } + + @Test + public void testAsyncTransactionManagerReturnsCommitStats() throws InterruptedException { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + try (AsyncTransactionManager manager = client.transactionManagerAsync(Options.commitStats())) { + TransactionContextFuture context = manager.beginAsync(); + while (true) { + try { + get( + context + .then( + new AsyncTransactionFunction() { + @Override + public ApiFuture apply(TransactionContext transaction, Void input) + throws Exception { + transaction.buffer( + Mutation.newInsertOrUpdateBuilder(TABLE_NAME) + .set("Key") + .to("k_commit_stats") + .set("StringValue") + .to("Should return commit stats") + .build()); + return ApiFutures.immediateFuture(null); + } + }, + executor) + .commitAsync()); + assertNotNull(get(manager.getCommitResponse()).getCommitStats()); + assertEquals(4L, get(manager.getCommitResponse()).getCommitStats().getMutationCount()); + break; + } catch (AbortedException e) { + Thread.sleep(e.getRetryDelayInMillis()); + context = manager.resetForRetryAsync(); + } + } + } + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java index 0fbd0702ec5..e9b98871847 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java @@ -17,6 +17,9 @@ package com.google.cloud.spanner.it; import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; +import static com.google.cloud.spanner.testing.TimestampHelper.afterMinutes; +import static com.google.cloud.spanner.testing.TimestampHelper.daysAgo; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -58,7 +61,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -87,7 +89,6 @@ public class ITBackupTest { private InstanceAdminClient instanceAdminClient; private Instance instance; private RemoteSpannerHelper testHelper; - private final AtomicInteger backupSeq = new AtomicInteger(); private List databases = new ArrayList<>(); private List backups = new ArrayList<>(); private final Random random = new Random(); @@ -188,34 +189,6 @@ private void waitForDbOperations(String backupId) throws InterruptedException { } } - private String getUniqueBackupId() { - return String.format("testbck_%06d_%04d", random.nextInt(1000000), backupSeq.incrementAndGet()); - } - - private static Timestamp after7Days() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(7L, TimeUnit.DAYS)); - } - - private Timestamp after5Minutes() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(5L, TimeUnit.MINUTES)); - } - - private Timestamp tomorrow() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(1L, TimeUnit.DAYS)); - } - - private Timestamp yesterday() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - - TimeUnit.MICROSECONDS.convert(1L, TimeUnit.DAYS)); - } - @Test public void testBackups() throws InterruptedException, ExecutionException { // Create two test databases in parallel. @@ -250,9 +223,9 @@ public void testBackups() throws InterruptedException, ExecutionException { .build())); // Create two backups in parallel. - String backupId1 = getUniqueBackupId() + "_bck1"; - String backupId2 = getUniqueBackupId() + "_bck2"; - Timestamp expireTime = after7Days(); + String backupId1 = testHelper.getUniqueBackupId() + "_bck1"; + String backupId2 = testHelper.getUniqueBackupId() + "_bck2"; + Timestamp expireTime = afterDays(7); logger.info(String.format("Creating backups %s and %s in parallel", backupId1, backupId2)); OperationFuture op1 = dbAdminClient.createBackup( @@ -414,8 +387,8 @@ private void testMetadata( private void testCreateInvalidExpirationDate(Database db) throws InterruptedException { // This is not allowed, the expiration date must be at least 6 hours in the future. - Timestamp expireTime = yesterday(); - String backupId = getUniqueBackupId(); + Timestamp expireTime = daysAgo(1); + String backupId = testHelper.getUniqueBackupId(); logger.info(String.format("Creating backup %s with invalid expiration date", backupId)); OperationFuture op = dbAdminClient.createBackup( @@ -437,8 +410,8 @@ private void testCreateInvalidExpirationDate(Database db) throws InterruptedExce private void testCancelBackupOperation(Database db) throws InterruptedException, ExecutionException { - Timestamp expireTime = after7Days(); - String backupId = getUniqueBackupId(); + Timestamp expireTime = afterDays(7); + String backupId = testHelper.getUniqueBackupId(); logger.info(String.format("Starting to create backup %s", backupId)); OperationFuture op = dbAdminClient.createBackup( @@ -477,7 +450,7 @@ private void testGetBackup(Database db, String backupId, Timestamp expireTime) { private void testUpdateBackup(Backup backup) { // Update the expire time. - Timestamp tomorrow = tomorrow(); + Timestamp tomorrow = afterDays(1); backup = backup.toBuilder().setExpireTime(tomorrow).build(); logger.info( String.format("Updating expire time of backup %s to 1 week", backup.getId().getBackup())); @@ -488,7 +461,7 @@ private void testUpdateBackup(Backup backup) { assertThat(backup.getExpireTime()).isEqualTo(tomorrow); // Try to set the expire time to 5 minutes in the future. - Timestamp in5Minutes = after5Minutes(); + Timestamp in5Minutes = afterMinutes(5); backup = backup.toBuilder().setExpireTime(in5Minutes).build(); try { logger.info( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java new file mode 100644 index 00000000000..bfdaa60a258 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -0,0 +1,244 @@ +/* + * 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.google.cloud.spanner.it; + +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; +import static com.google.cloud.spanner.testing.TimestampHelper.daysAgo; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.paging.Page; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.InstanceId; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; +import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrBackupAndRestore { + private static final Logger logger = Logger.getLogger(ITPitrBackupAndRestore.class.getName()); + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static final long OP_TIMEOUT = 10; + private static final TimeUnit OP_TIMEOUT_UNIT = TimeUnit.MINUTES; + private static RemoteSpannerHelper testHelper; + private static DatabaseAdminClient dbAdminClient; + private static Database testDatabase; + private static final List backupsToDrop = new ArrayList<>(); + private static final List databasesToDrop = new ArrayList<>(); + + @BeforeClass + public static void doNotRunOnEmulator() { + assumeFalse("PITR features are not supported by the emulator", isUsingEmulator()); + } + + @BeforeClass + public static void setUp() throws Exception { + testHelper = env.getTestHelper(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + testDatabase = createTestDatabase(); + } + + @AfterClass + public static void tearDown() { + int numDropped = 0; + for (Database database : databasesToDrop) { + try { + database.drop(); + numDropped++; + } catch (SpannerException e) { + logger.log(Level.SEVERE, "Failed to drop test database " + database.getId(), e); + } + } + logger.log(Level.INFO, "Dropped {0} test databases(s)", numDropped); + + numDropped = 0; + for (Backup backup : backupsToDrop) { + try { + backup.delete(); + numDropped++; + } catch (SpannerException e) { + logger.log(Level.SEVERE, "Failed to drop test backup " + backup.getId(), e); + } + } + logger.log(Level.INFO, "Dropped {0} test backup(s)", numDropped); + } + + @Test + @Ignore("backup and restore for pitr is not released yet") + public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() throws Exception { + final DatabaseId backupDatabaseId = testDatabase.getId(); + final String restoreDatabaseId = testHelper.getUniqueDatabaseId(); + final String projectId = backupDatabaseId.getInstanceId().getProject(); + final String instanceId = backupDatabaseId.getInstanceId().getInstance(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = testDatabase.getEarliestVersionTime(); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) + .setDatabase(backupDatabaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + final Backup createdBackup = createBackup(backupToCreate); + assertThat(createdBackup.getVersionTime()).isEqualTo(versionTime); + + final RestoreDatabaseMetadata restoreDatabaseMetadata = + restoreDatabase(instanceId, backupId, restoreDatabaseId); + assertThat(Timestamp.fromProto(restoreDatabaseMetadata.getBackupInfo().getVersionTime())) + .isEqualTo(versionTime); + + final Database retrievedDatabase = dbAdminClient.getDatabase(instanceId, restoreDatabaseId); + assertThat(retrievedDatabase).isNotNull(); + assertThat( + Timestamp.fromProto( + retrievedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime())) + .isEqualTo(versionTime); + + final Database listedDatabase = listDatabase(instanceId, restoreDatabaseId); + assertThat(listedDatabase).isNotNull(); + assertThat( + Timestamp.fromProto( + listedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime())) + .isEqualTo(versionTime); + } + + @Test(expected = SpannerException.class) + @Ignore("backup and restore for pitr is not released yet") + public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception { + final DatabaseId databaseId = testDatabase.getId(); + final InstanceId instanceId = databaseId.getInstanceId(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = daysAgo(30); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(instanceId, backupId)) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + createBackup(backupToCreate); + } + + @Test(expected = SpannerException.class) + @Ignore("backup and restore for pitr is not released yet") + public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { + final DatabaseId databaseId = testDatabase.getId(); + final InstanceId instanceId = databaseId.getInstanceId(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = afterDays(1); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(instanceId, backupId)) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + createBackup(backupToCreate); + } + + private Backup createBackup(Backup backupToCreate) + throws InterruptedException, ExecutionException, TimeoutException { + final Backup createdBackup = getOrThrow(dbAdminClient.createBackup(backupToCreate)); + backupsToDrop.add(createdBackup); + return createdBackup; + } + + private RestoreDatabaseMetadata restoreDatabase( + String instanceId, String backupId, String databaseId) + throws InterruptedException, ExecutionException, TimeoutException { + final OperationFuture op = + dbAdminClient.restoreDatabase(instanceId, backupId, instanceId, databaseId); + final Database database = getOrThrow(op); + databasesToDrop.add(database); + return op.getMetadata().get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + } + + private Database listDatabase(String instanceId, String databaseId) { + Page page = dbAdminClient.listDatabases(instanceId); + while (page != null) { + for (Database database : page.getValues()) { + if (database.getId().getDatabase().equals(databaseId)) { + return database; + } + } + page = page.getNextPage(); + } + return null; + } + + private static Database createTestDatabase() + throws InterruptedException, ExecutionException, TimeoutException { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final OperationFuture op = + dbAdminClient.createDatabase( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '7d')")); + final Database database = getOrThrow(op); + databasesToDrop.add(database); + return database; + } + + private static T getOrThrow(OperationFuture op) + throws TimeoutException, InterruptedException, ExecutionException { + try { + return op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + } catch (ExecutionException e) { + if (e.getCause() != null && e.getCause() instanceof SpannerException) { + throw (SpannerException) e.getCause(); + } else { + throw e; + } + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java new file mode 100644 index 00000000000..f9fa081e413 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java @@ -0,0 +1,144 @@ +/* + * 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. + * 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.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseNotFoundException; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.After; +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.JUnit4; +import org.threeten.bp.Duration; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrCreateDatabaseTest { + + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(2); + private static final String VERSION_RETENTION_PERIOD = "7d"; + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private RemoteSpannerHelper testHelper; + private DatabaseAdminClient dbAdminClient; + private List databasesToDrop; + + @BeforeClass + public static void doNotRunOnEmulator() { + assumeFalse("PITR-lite features are not supported by the emulator", isUsingEmulator()); + } + + @Before + public void setUp() { + testHelper = env.getTestHelper(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + databasesToDrop = new ArrayList<>(); + } + + @After + public void tearDown() { + for (Database database : databasesToDrop) { + final DatabaseId id = database.getId(); + dbAdminClient.dropDatabase(id.getInstanceId().getInstance(), id.getDatabase()); + } + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughCreateDatabase() throws Exception { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '" + + VERSION_RETENTION_PERIOD + + "')"; + + final Database database = createDatabase(instanceId, databaseId, extraStatement); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() throws Exception { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '" + + VERSION_RETENTION_PERIOD + + "')"; + + createDatabase(instanceId, databaseId, extraStatement); + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test(expected = DatabaseNotFoundException.class) + public void returnsAnErrorWhenAnInvalidVersionRetentionPeriodIsGiven() { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '0d')"; + + try { + createDatabase(instanceId, databaseId, extraStatement); + fail("Expected invalid argument error when setting invalid version retention period"); + } catch (Exception e) { + SpannerException spannerException = (SpannerException) e.getCause(); + assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + + // Expects a database not found exception + dbAdminClient.getDatabase(instanceId, databaseId); + } + + private Database createDatabase( + final String instanceId, final String databaseId, final String extraStatement) + throws Exception { + final Database database = + dbAdminClient + .createDatabase(instanceId, databaseId, Collections.singletonList(extraStatement)) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + databasesToDrop.add(database); + + return database; + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java new file mode 100644 index 00000000000..72a52b39078 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java @@ -0,0 +1,207 @@ +/* + * 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. + * 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.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.paging.Page; +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.ErrorCode; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +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.JUnit4; +import org.threeten.bp.Duration; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrUpdateDatabaseTest { + + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(2); + private static final String VERSION_RETENTION_PERIOD = "7d"; + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static DatabaseAdminClient dbAdminClient; + private static DatabaseClient dbClient; + private static String instanceId; + private static String databaseId; + private static UpdateDatabaseDdlMetadata metadata; + + @BeforeClass + public static void setUp() throws Exception { + assumeFalse("PITR-lite features are not supported by the emulator", isUsingEmulator()); + + final RemoteSpannerHelper testHelper = env.getTestHelper(); + final String projectId = testHelper.getOptions().getProjectId(); + instanceId = testHelper.getInstanceId().getInstance(); + databaseId = testHelper.getUniqueDatabaseId(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + + createDatabase(dbAdminClient, instanceId, databaseId, Collections.emptyList()); + metadata = + updateVersionRetentionPeriod( + dbAdminClient, instanceId, databaseId, VERSION_RETENTION_PERIOD); + + dbClient = + testHelper.getClient().getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + } + + @AfterClass + public static void tearDown() { + if (!isUsingEmulator()) { + dbAdminClient.dropDatabase(instanceId, databaseId); + } + } + + @Test + public void checksThatTheOperationWasNotThrottled() { + assertThat(metadata.getThrottled()).isFalse(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughListDatabases() { + final Page page = dbAdminClient.listDatabases(instanceId); + + for (Database database : page.iterateAll()) { + if (!database.getId().getDatabase().equals(databaseId)) { + continue; + } + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { + final List ddls = dbAdminClient.getDatabaseDdl(instanceId, databaseId); + + boolean hasVersionRetentionPeriodStatement = false; + for (String ddl : ddls) { + hasVersionRetentionPeriodStatement = + ddl.contains("version_retention_period = '" + VERSION_RETENTION_PERIOD + "'"); + if (hasVersionRetentionPeriodStatement) { + break; + } + } + assertThat(hasVersionRetentionPeriodStatement).isTrue(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { + try (final ResultSet rs = + dbClient + .singleUse() + .executeQuery( + Statement.of( + "SELECT OPTION_VALUE AS version_retention_period " + + "FROM INFORMATION_SCHEMA.DATABASE_OPTIONS " + + "WHERE SCHEMA_NAME = '' AND OPTION_NAME = 'version_retention_period'"))) { + + String versionRetentionPeriod = null; + while (rs.next()) { + versionRetentionPeriod = rs.getString("version_retention_period"); + } + + assertThat(versionRetentionPeriod).isEqualTo(VERSION_RETENTION_PERIOD); + } + } + + @Test + public void returnsAnErrorWhenAnInvalidRetentionPeriodIsGiven() { + try { + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '0d')"), + "op_invalid_retention_period_" + databaseId) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + fail("Expected invalid argument error when setting invalid version retention period"); + } catch (Exception e) { + SpannerException spannerException = (SpannerException) e.getCause(); + assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + private static Database createDatabase( + final DatabaseAdminClient dbAdminClient, + final String instanceId, + final String databaseId, + final Iterable extraStatements) + throws Exception { + return dbAdminClient + .createDatabase(instanceId, databaseId, extraStatements) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + } + + private static UpdateDatabaseDdlMetadata updateVersionRetentionPeriod( + final DatabaseAdminClient dbAdminClient, + final String instanceId, + final String databaseId, + final String versionRetentionPeriod) + throws Exception { + final OperationFuture op = + dbAdminClient.updateDatabaseDdl( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + + databaseId + + " SET OPTIONS ( version_retention_period = '" + + versionRetentionPeriod + + "' )"), + "updateddl_version_retention_period"); + op.get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + return op.getMetadata().get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java index 4d65af67ed0..3ea4a067717 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java @@ -18,6 +18,7 @@ import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -29,6 +30,7 @@ import com.google.cloud.spanner.Key; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Struct; @@ -212,4 +214,31 @@ public void abortAndRetry() throws InterruptedException { manager2.close(); } } + + @SuppressWarnings("resource") + @Test + public void testTransactionManagerReturnsCommitStats() throws InterruptedException { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + try (TransactionManager manager = client.transactionManager(Options.commitStats())) { + TransactionContext transaction = manager.begin(); + while (true) { + transaction.buffer( + Mutation.newInsertBuilder("T") + .set("K") + .to("KeyCommitStats") + .set("BoolValue") + .to(true) + .build()); + try { + manager.commit(); + assertNotNull(manager.getCommitResponse().getCommitStats()); + assertEquals(2L, manager.getCommitResponse().getCommitStats().getMutationCount()); + break; + } catch (AbortedException e) { + Thread.sleep(e.getRetryDelayInMillis()); + transaction = manager.resetForRetry(); + } + } + } + } } 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 503f0ddf908..ed2bc564b06 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 @@ -19,6 +19,8 @@ import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException; import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -33,6 +35,7 @@ import com.google.cloud.spanner.Key; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.PartitionOptions; import com.google.cloud.spanner.ReadContext; @@ -630,4 +633,23 @@ public Long run(TransactionContext transaction) throws Exception { assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); } } + + @Test + public void testTransactionRunnerReturnsCommitStats() { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + final String key = uniqueKey(); + TransactionRunner runner = client.readWriteTransaction(Options.commitStats()); + runner.run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + transaction.buffer( + Mutation.newInsertBuilder("T").set("K").to(key).set("V").to(0).build()); + return null; + } + }); + assertNotNull(runner.getCommitResponse().getCommitStats()); + // MutationCount = 2 (2 columns). + assertEquals(2L, runner.getCommitResponse().getCommitStats().getMutationCount()); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java index 6f11fc6b438..3cf9d387a15 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java @@ -17,7 +17,9 @@ package com.google.cloud.spanner.it; import static com.google.cloud.spanner.SpannerMatchers.isSpannerException; +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -25,6 +27,7 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.CommitResponse; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.ErrorCode; @@ -32,6 +35,7 @@ import com.google.cloud.spanner.Key; import com.google.cloud.spanner.KeySet; import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SpannerException; @@ -157,6 +161,44 @@ public void writeAtLeastOnce() { assertThat(row.getString(0)).isEqualTo("v1"); } + @Test + public void testWriteReturnsCommitStats() { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + CommitResponse response = + client.writeWithOptions( + Arrays.asList( + Mutation.newInsertOrUpdateBuilder("T") + .set("K") + .to(lastKey = uniqueString()) + .set("StringValue") + .to("v1") + .build()), + Options.commitStats()); + assertNotNull(response); + assertNotNull(response.getCommitTimestamp()); + assertNotNull(response.getCommitStats()); + assertEquals(2L, response.getCommitStats().getMutationCount()); + } + + @Test + public void testWriteAtLeastOnceReturnsCommitStats() { + assumeFalse("Emulator does not return commit statistics", isUsingEmulator()); + CommitResponse response = + client.writeAtLeastOnceWithOptions( + Arrays.asList( + Mutation.newInsertOrUpdateBuilder("T") + .set("K") + .to(lastKey = uniqueString()) + .set("StringValue") + .to("v1") + .build()), + Options.commitStats()); + assertNotNull(response); + assertNotNull(response.getCommitTimestamp()); + assertNotNull(response.getCommitStats()); + assertEquals(2L, response.getCommitStats().getMutationCount()); + } + @Test public void writeAlreadyExists() { client.write( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java index 84aaa91bcf5..5a9dec72edc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java @@ -24,7 +24,9 @@ import static org.junit.Assume.assumeTrue; import com.google.api.core.ApiFunction; +import com.google.api.gax.core.GaxProperties; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.HeaderProvider; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.OAuth2Credentials; import com.google.cloud.spanner.DatabaseAdminClient; @@ -151,6 +153,8 @@ public class GapicSpannerRpcTest { private Server server; private InetSocketAddress address; private final Map optionsMap = new HashMap<>(); + private Metadata seenHeaders; + private String defaultUserAgent; @BeforeClass public static void checkNotEmulator() { @@ -161,6 +165,7 @@ public static void checkNotEmulator() { @Before public void startServer() throws IOException { + defaultUserAgent = "spanner-java/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class); mockSpanner = new MockSpannerServiceImpl(); mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions. mockSpanner.putStatementResult(StatementResult.query(SELECT1AND2, SELECT1_RESULTSET)); @@ -183,6 +188,7 @@ public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next) { + seenHeaders = headers; String auth = headers.get(Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)); assertThat(auth).isEqualTo("Bearer " + VARIABLE_OAUTH_TOKEN); @@ -502,6 +508,46 @@ public void testAdminRequestsLimitExceededRetryAlgorithm() { assertThat(alg.shouldRetry(new Exception("random exception"), null)).isFalse(); } + @Test + public void testDefaultUserAgent() { + final SpannerOptions options = createSpannerOptions(); + final Spanner spanner = options.getService(); + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) + .contains(defaultUserAgent); + } + + @Test + public void testCustomUserAgent() { + final HeaderProvider userAgentHeaderProvider = + new HeaderProvider() { + @Override + public Map getHeaders() { + final Map headers = new HashMap<>(); + headers.put("user-agent", "test-agent"); + return headers; + } + }; + final SpannerOptions options = + createSpannerOptions().toBuilder().setHeaderProvider(userAgentHeaderProvider).build(); + final Spanner spanner = options.getService(); + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) + .contains("test-agent " + defaultUserAgent); + } + @SuppressWarnings("rawtypes") private SpannerOptions createSpannerOptions() { String endpoint = address.getHostString() + ":" + server.getPort(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/v1/SpannerClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/v1/SpannerClientTest.java index a31cc5f2a19..0ee0e706d1f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/v1/SpannerClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/v1/SpannerClientTest.java @@ -954,7 +954,10 @@ public void beginTransactionExceptionTest2() throws Exception { @Test public void commitTest() throws Exception { CommitResponse expectedResponse = - CommitResponse.newBuilder().setCommitTimestamp(Timestamp.newBuilder().build()).build(); + CommitResponse.newBuilder() + .setCommitTimestamp(Timestamp.newBuilder().build()) + .setCommitStats(CommitResponse.CommitStats.newBuilder().build()) + .build(); mockSpanner.addResponse(expectedResponse); SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]"); @@ -996,7 +999,10 @@ public void commitExceptionTest() throws Exception { @Test public void commitTest2() throws Exception { CommitResponse expectedResponse = - CommitResponse.newBuilder().setCommitTimestamp(Timestamp.newBuilder().build()).build(); + CommitResponse.newBuilder() + .setCommitTimestamp(Timestamp.newBuilder().build()) + .setCommitStats(CommitResponse.CommitStats.newBuilder().build()) + .build(); mockSpanner.addResponse(expectedResponse); SessionName session = SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]"); @@ -1038,7 +1044,10 @@ public void commitExceptionTest2() throws Exception { @Test public void commitTest3() throws Exception { CommitResponse expectedResponse = - CommitResponse.newBuilder().setCommitTimestamp(Timestamp.newBuilder().build()).build(); + CommitResponse.newBuilder() + .setCommitTimestamp(Timestamp.newBuilder().build()) + .setCommitStats(CommitResponse.CommitStats.newBuilder().build()) + .build(); mockSpanner.addResponse(expectedResponse); String session = "session1984987798"; @@ -1080,7 +1089,10 @@ public void commitExceptionTest3() throws Exception { @Test public void commitTest4() throws Exception { CommitResponse expectedResponse = - CommitResponse.newBuilder().setCommitTimestamp(Timestamp.newBuilder().build()).build(); + CommitResponse.newBuilder() + .setCommitTimestamp(Timestamp.newBuilder().build()) + .setCommitStats(CommitResponse.CommitStats.newBuilder().build()) + .build(); mockSpanner.addResponse(expectedResponse); String session = "session1984987798"; diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index 1a7a0d889fb..5ee7143baf5 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 - 3.3.2 + 4.0.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 - 3.3.2 + 4.0.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index f51f2e89dc2..1ba5438a566 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 - 3.3.2 + 4.0.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 - 3.3.2 + 4.0.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 059e8e02945..15ec20312d7 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 - 3.3.2 + 4.0.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 3.3.2 + 4.0.0 diff --git a/grpc-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerGrpc.java b/grpc-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerGrpc.java index 2ea1647918f..52e4daaaf13 100644 --- a/grpc-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerGrpc.java +++ b/grpc-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerGrpc.java @@ -924,6 +924,11 @@ public void beginTransaction( * transactions. However, it can also happen for a variety of other * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt * the transaction from the beginning, re-using the same session. + * On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, + * for example, if the client job experiences a 1+ hour networking failure. + * At that point, Cloud Spanner has lost track of the transaction outcome and + * we recommend that you perform another read from the database to see the + * state of things as they are now. * */ public void commit( @@ -1357,6 +1362,11 @@ public void beginTransaction( * transactions. However, it can also happen for a variety of other * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt * the transaction from the beginning, re-using the same session. + * On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, + * for example, if the client job experiences a 1+ hour networking failure. + * At that point, Cloud Spanner has lost track of the transaction outcome and + * we recommend that you perform another read from the database to see the + * state of things as they are now. * */ public void commit( @@ -1663,6 +1673,11 @@ public com.google.spanner.v1.Transaction beginTransaction( * transactions. However, it can also happen for a variety of other * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt * the transaction from the beginning, re-using the same session. + * On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, + * for example, if the client job experiences a 1+ hour networking failure. + * At that point, Cloud Spanner has lost track of the transaction outcome and + * we recommend that you perform another read from the database to see the + * state of things as they are now. * */ public com.google.spanner.v1.CommitResponse commit( @@ -1932,6 +1947,11 @@ public com.google.common.util.concurrent.ListenableFuture */ public com.google.common.util.concurrent.ListenableFuture diff --git a/pom.xml b/pom.xml index f8d4aa31c19..55f71d6c3cf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 3.3.2 + 4.0.0 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 0.9.4 + 0.10.0 @@ -71,37 +71,37 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 3.3.2 + 4.0.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 3.3.2 + 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 3.3.2 + 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 3.3.2 + 4.0.0 com.google.cloud google-cloud-spanner - 3.3.2 + 4.0.0 @@ -115,7 +115,7 @@ junit junit - 4.13.1 + 4.13.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 f4c05bbb57f..a67c464ecd0 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 - 3.3.2 + 4.0.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 - 3.3.2 + 4.0.0 diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Backup.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Backup.java index 0d9230003e1..935d46f59df 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Backup.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Backup.java @@ -138,6 +138,21 @@ private Backup( mutable_bitField0_ |= 0x00000001; } referencingDatabases_.add(s); + break; + } + case 74: + { + com.google.protobuf.Timestamp.Builder subBuilder = null; + if (versionTime_ != null) { + subBuilder = versionTime_.toBuilder(); + } + versionTime_ = + input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(versionTime_); + versionTime_ = subBuilder.buildPartial(); + } + break; } default: @@ -393,6 +408,61 @@ public com.google.protobuf.ByteString getDatabaseBytes() { } } + public static final int VERSION_TIME_FIELD_NUMBER = 9; + private com.google.protobuf.Timestamp versionTime_; + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return Whether the versionTime field is set. + */ + @java.lang.Override + public boolean hasVersionTime() { + return versionTime_ != null; + } + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return The versionTime. + */ + @java.lang.Override + public com.google.protobuf.Timestamp getVersionTime() { + return versionTime_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : versionTime_; + } + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + @java.lang.Override + public com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder() { + return getVersionTime(); + } + public static final int EXPIRE_TIME_FIELD_NUMBER = 3; private com.google.protobuf.Timestamp expireTime_; /** @@ -529,10 +599,9 @@ public com.google.protobuf.ByteString getNameBytes() { * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; @@ -548,10 +617,9 @@ public boolean hasCreateTime() { * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; @@ -567,10 +635,9 @@ public com.google.protobuf.Timestamp getCreateTime() { * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; @@ -764,6 +831,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io com.google.protobuf.GeneratedMessageV3.writeString( output, 7, referencingDatabases_.getRaw(i)); } + if (versionTime_ != null) { + output.writeMessage(9, getVersionTime()); + } unknownFields.writeTo(output); } @@ -799,6 +869,9 @@ public int getSerializedSize() { size += dataSize; size += 1 * getReferencingDatabasesList().size(); } + if (versionTime_ != null) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(9, getVersionTime()); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -816,6 +889,10 @@ public boolean equals(final java.lang.Object obj) { (com.google.spanner.admin.database.v1.Backup) obj; if (!getDatabase().equals(other.getDatabase())) return false; + if (hasVersionTime() != other.hasVersionTime()) return false; + if (hasVersionTime()) { + if (!getVersionTime().equals(other.getVersionTime())) return false; + } if (hasExpireTime() != other.hasExpireTime()) return false; if (hasExpireTime()) { if (!getExpireTime().equals(other.getExpireTime())) return false; @@ -841,6 +918,10 @@ public int hashCode() { hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + DATABASE_FIELD_NUMBER; hash = (53 * hash) + getDatabase().hashCode(); + if (hasVersionTime()) { + hash = (37 * hash) + VERSION_TIME_FIELD_NUMBER; + hash = (53 * hash) + getVersionTime().hashCode(); + } if (hasExpireTime()) { hash = (37 * hash) + EXPIRE_TIME_FIELD_NUMBER; hash = (53 * hash) + getExpireTime().hashCode(); @@ -1006,6 +1087,12 @@ public Builder clear() { super.clear(); database_ = ""; + if (versionTimeBuilder_ == null) { + versionTime_ = null; + } else { + versionTime_ = null; + versionTimeBuilder_ = null; + } if (expireTimeBuilder_ == null) { expireTime_ = null; } else { @@ -1055,6 +1142,11 @@ public com.google.spanner.admin.database.v1.Backup buildPartial() { new com.google.spanner.admin.database.v1.Backup(this); int from_bitField0_ = bitField0_; result.database_ = database_; + if (versionTimeBuilder_ == null) { + result.versionTime_ = versionTime_; + } else { + result.versionTime_ = versionTimeBuilder_.build(); + } if (expireTimeBuilder_ == null) { result.expireTime_ = expireTime_; } else { @@ -1126,6 +1218,9 @@ public Builder mergeFrom(com.google.spanner.admin.database.v1.Backup other) { database_ = other.database_; onChanged(); } + if (other.hasVersionTime()) { + mergeVersionTime(other.getVersionTime()); + } if (other.hasExpireTime()) { mergeExpireTime(other.getExpireTime()); } @@ -1309,6 +1404,218 @@ public Builder setDatabaseBytes(com.google.protobuf.ByteString value) { return this; } + private com.google.protobuf.Timestamp versionTime_; + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + versionTimeBuilder_; + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return Whether the versionTime field is set. + */ + public boolean hasVersionTime() { + return versionTimeBuilder_ != null || versionTime_ != null; + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return The versionTime. + */ + public com.google.protobuf.Timestamp getVersionTime() { + if (versionTimeBuilder_ == null) { + return versionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : versionTime_; + } else { + return versionTimeBuilder_.getMessage(); + } + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public Builder setVersionTime(com.google.protobuf.Timestamp value) { + if (versionTimeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + versionTime_ = value; + onChanged(); + } else { + versionTimeBuilder_.setMessage(value); + } + + return this; + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public Builder setVersionTime(com.google.protobuf.Timestamp.Builder builderForValue) { + if (versionTimeBuilder_ == null) { + versionTime_ = builderForValue.build(); + onChanged(); + } else { + versionTimeBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public Builder mergeVersionTime(com.google.protobuf.Timestamp value) { + if (versionTimeBuilder_ == null) { + if (versionTime_ != null) { + versionTime_ = + com.google.protobuf.Timestamp.newBuilder(versionTime_) + .mergeFrom(value) + .buildPartial(); + } else { + versionTime_ = value; + } + onChanged(); + } else { + versionTimeBuilder_.mergeFrom(value); + } + + return this; + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public Builder clearVersionTime() { + if (versionTimeBuilder_ == null) { + versionTime_ = null; + onChanged(); + } else { + versionTime_ = null; + versionTimeBuilder_ = null; + } + + return this; + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public com.google.protobuf.Timestamp.Builder getVersionTimeBuilder() { + + onChanged(); + return getVersionTimeFieldBuilder().getBuilder(); + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + public com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder() { + if (versionTimeBuilder_ != null) { + return versionTimeBuilder_.getMessageOrBuilder(); + } else { + return versionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : versionTime_; + } + } + /** + * + * + *
    +     * The backup will contain an externally consistent copy of the database at
    +     * the timestamp specified by `version_time`. If `version_time` is not
    +     * specified, the system will set `version_time` to the `create_time` of the
    +     * backup.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + getVersionTimeFieldBuilder() { + if (versionTimeBuilder_ == null) { + versionTimeBuilder_ = + new com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder>( + getVersionTime(), getParentForChildren(), isClean()); + versionTime_ = null; + } + return versionTimeBuilder_; + } + private com.google.protobuf.Timestamp expireTime_; private com.google.protobuf.SingleFieldBuilderV3< com.google.protobuf.Timestamp, @@ -1703,10 +2010,9 @@ public Builder setNameBytes(com.google.protobuf.ByteString value) { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1722,10 +2028,9 @@ public boolean hasCreateTime() { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1747,10 +2052,9 @@ public com.google.protobuf.Timestamp getCreateTime() { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1774,10 +2078,9 @@ public Builder setCreateTime(com.google.protobuf.Timestamp value) { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1798,10 +2101,9 @@ public Builder setCreateTime(com.google.protobuf.Timestamp.Builder builderForVal * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1827,10 +2129,9 @@ public Builder mergeCreateTime(com.google.protobuf.Timestamp value) { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1852,10 +2153,9 @@ public Builder clearCreateTime() { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1871,10 +2171,9 @@ public com.google.protobuf.Timestamp.Builder getCreateTimeBuilder() { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * @@ -1894,10 +2193,9 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * * *
    -     * Output only. The backup will contain an externally consistent
    -     * copy of the database at the timestamp specified by
    -     * `create_time`. `create_time` is approximately the time the
    -     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +     * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +     * request is received. If the request does not specify `version_time`, the
    +     * `version_time` of the backup will be equivalent to the `create_time`.
          * 
    * * diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfo.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfo.java index 99bc0f10191..56ce510e170 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfo.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfo.java @@ -98,6 +98,21 @@ private BackupInfo( java.lang.String s = input.readStringRequireUtf8(); sourceDatabase_ = s; + break; + } + case 34: + { + com.google.protobuf.Timestamp.Builder subBuilder = null; + if (versionTime_ != null) { + subBuilder = versionTime_.toBuilder(); + } + versionTime_ = + input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(versionTime_); + versionTime_ = subBuilder.buildPartial(); + } + break; } default: @@ -143,7 +158,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The backup. */ @@ -166,7 +181,7 @@ public java.lang.String getBackup() { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for backup. */ @@ -183,14 +198,72 @@ public com.google.protobuf.ByteString getBackupBytes() { } } + public static final int VERSION_TIME_FIELD_NUMBER = 4; + private com.google.protobuf.Timestamp versionTime_; + /** + * + * + *
    +   * The backup contains an externally consistent copy of `source_database` at
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return Whether the versionTime field is set. + */ + @java.lang.Override + public boolean hasVersionTime() { + return versionTime_ != null; + } + /** + * + * + *
    +   * The backup contains an externally consistent copy of `source_database` at
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return The versionTime. + */ + @java.lang.Override + public com.google.protobuf.Timestamp getVersionTime() { + return versionTime_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : versionTime_; + } + /** + * + * + *
    +   * The backup contains an externally consistent copy of `source_database` at
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + @java.lang.Override + public com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder() { + return getVersionTime(); + } + public static final int CREATE_TIME_FIELD_NUMBER = 2; private com.google.protobuf.Timestamp createTime_; /** * * *
    -   * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -205,8 +278,8 @@ public boolean hasCreateTime() { * * *
    -   * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -221,8 +294,8 @@ public com.google.protobuf.Timestamp getCreateTime() { * * *
    -   * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -241,7 +314,7 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The sourceDatabase. */ @@ -264,7 +337,7 @@ public java.lang.String getSourceDatabase() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The bytes for sourceDatabase. */ @@ -304,6 +377,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io if (!getSourceDatabaseBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 3, sourceDatabase_); } + if (versionTime_ != null) { + output.writeMessage(4, getVersionTime()); + } unknownFields.writeTo(output); } @@ -322,6 +398,9 @@ public int getSerializedSize() { if (!getSourceDatabaseBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, sourceDatabase_); } + if (versionTime_ != null) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(4, getVersionTime()); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -339,6 +418,10 @@ public boolean equals(final java.lang.Object obj) { (com.google.spanner.admin.database.v1.BackupInfo) obj; if (!getBackup().equals(other.getBackup())) return false; + if (hasVersionTime() != other.hasVersionTime()) return false; + if (hasVersionTime()) { + if (!getVersionTime().equals(other.getVersionTime())) return false; + } if (hasCreateTime() != other.hasCreateTime()) return false; if (hasCreateTime()) { if (!getCreateTime().equals(other.getCreateTime())) return false; @@ -357,6 +440,10 @@ public int hashCode() { hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + BACKUP_FIELD_NUMBER; hash = (53 * hash) + getBackup().hashCode(); + if (hasVersionTime()) { + hash = (37 * hash) + VERSION_TIME_FIELD_NUMBER; + hash = (53 * hash) + getVersionTime().hashCode(); + } if (hasCreateTime()) { hash = (37 * hash) + CREATE_TIME_FIELD_NUMBER; hash = (53 * hash) + getCreateTime().hashCode(); @@ -510,6 +597,12 @@ public Builder clear() { super.clear(); backup_ = ""; + if (versionTimeBuilder_ == null) { + versionTime_ = null; + } else { + versionTime_ = null; + versionTimeBuilder_ = null; + } if (createTimeBuilder_ == null) { createTime_ = null; } else { @@ -546,6 +639,11 @@ public com.google.spanner.admin.database.v1.BackupInfo buildPartial() { com.google.spanner.admin.database.v1.BackupInfo result = new com.google.spanner.admin.database.v1.BackupInfo(this); result.backup_ = backup_; + if (versionTimeBuilder_ == null) { + result.versionTime_ = versionTime_; + } else { + result.versionTime_ = versionTimeBuilder_.build(); + } if (createTimeBuilder_ == null) { result.createTime_ = createTime_; } else { @@ -606,6 +704,9 @@ public Builder mergeFrom(com.google.spanner.admin.database.v1.BackupInfo other) backup_ = other.backup_; onChanged(); } + if (other.hasVersionTime()) { + mergeVersionTime(other.getVersionTime()); + } if (other.hasCreateTime()) { mergeCreateTime(other.getCreateTime()); } @@ -650,7 +751,7 @@ public Builder mergeFrom( * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The backup. */ @@ -672,7 +773,7 @@ public java.lang.String getBackup() { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for backup. */ @@ -694,7 +795,7 @@ public com.google.protobuf.ByteString getBackupBytes() { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @param value The backup to set. * @return This builder for chaining. @@ -715,7 +816,7 @@ public Builder setBackup(java.lang.String value) { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -732,7 +833,7 @@ public Builder clearBackup() { * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @param value The bytes for backup to set. * @return This builder for chaining. @@ -748,6 +849,227 @@ public Builder setBackupBytes(com.google.protobuf.ByteString value) { return this; } + private com.google.protobuf.Timestamp versionTime_; + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + versionTimeBuilder_; + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return Whether the versionTime field is set. + */ + public boolean hasVersionTime() { + return versionTimeBuilder_ != null || versionTime_ != null; + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return The versionTime. + */ + public com.google.protobuf.Timestamp getVersionTime() { + if (versionTimeBuilder_ == null) { + return versionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : versionTime_; + } else { + return versionTimeBuilder_.getMessage(); + } + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public Builder setVersionTime(com.google.protobuf.Timestamp value) { + if (versionTimeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + versionTime_ = value; + onChanged(); + } else { + versionTimeBuilder_.setMessage(value); + } + + return this; + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public Builder setVersionTime(com.google.protobuf.Timestamp.Builder builderForValue) { + if (versionTimeBuilder_ == null) { + versionTime_ = builderForValue.build(); + onChanged(); + } else { + versionTimeBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public Builder mergeVersionTime(com.google.protobuf.Timestamp value) { + if (versionTimeBuilder_ == null) { + if (versionTime_ != null) { + versionTime_ = + com.google.protobuf.Timestamp.newBuilder(versionTime_) + .mergeFrom(value) + .buildPartial(); + } else { + versionTime_ = value; + } + onChanged(); + } else { + versionTimeBuilder_.mergeFrom(value); + } + + return this; + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public Builder clearVersionTime() { + if (versionTimeBuilder_ == null) { + versionTime_ = null; + onChanged(); + } else { + versionTime_ = null; + versionTimeBuilder_ = null; + } + + return this; + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public com.google.protobuf.Timestamp.Builder getVersionTimeBuilder() { + + onChanged(); + return getVersionTimeFieldBuilder().getBuilder(); + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + public com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder() { + if (versionTimeBuilder_ != null) { + return versionTimeBuilder_.getMessageOrBuilder(); + } else { + return versionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : versionTime_; + } + } + /** + * + * + *
    +     * The backup contains an externally consistent copy of `source_database` at
    +     * the timestamp specified by `version_time`. If the
    +     * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +     * `version_time`, the `version_time` of the backup is equivalent to the
    +     * `create_time`.
    +     * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + getVersionTimeFieldBuilder() { + if (versionTimeBuilder_ == null) { + versionTimeBuilder_ = + new com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder>( + getVersionTime(), getParentForChildren(), isClean()); + versionTime_ = null; + } + return versionTimeBuilder_; + } + private com.google.protobuf.Timestamp createTime_; private com.google.protobuf.SingleFieldBuilderV3< com.google.protobuf.Timestamp, @@ -758,8 +1080,8 @@ public Builder setBackupBytes(com.google.protobuf.ByteString value) { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -773,8 +1095,8 @@ public boolean hasCreateTime() { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -794,8 +1116,8 @@ public com.google.protobuf.Timestamp getCreateTime() { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -817,8 +1139,8 @@ public Builder setCreateTime(com.google.protobuf.Timestamp value) { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -837,8 +1159,8 @@ public Builder setCreateTime(com.google.protobuf.Timestamp.Builder builderForVal * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -862,8 +1184,8 @@ public Builder mergeCreateTime(com.google.protobuf.Timestamp value) { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -883,8 +1205,8 @@ public Builder clearCreateTime() { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -898,8 +1220,8 @@ public com.google.protobuf.Timestamp.Builder getCreateTimeBuilder() { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -917,8 +1239,8 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * * *
    -     * The backup contains an externally consistent copy of `source_database` at
    -     * the timestamp specified by `create_time`.
    +     * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +     * received.
          * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -948,7 +1270,7 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The sourceDatabase. */ @@ -970,7 +1292,7 @@ public java.lang.String getSourceDatabase() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The bytes for sourceDatabase. */ @@ -992,7 +1314,7 @@ public com.google.protobuf.ByteString getSourceDatabaseBytes() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @param value The sourceDatabase to set. * @return This builder for chaining. @@ -1013,7 +1335,7 @@ public Builder setSourceDatabase(java.lang.String value) { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -1030,7 +1352,7 @@ public Builder clearSourceDatabase() { * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @param value The bytes for sourceDatabase to set. * @return This builder for chaining. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfoOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfoOrBuilder.java index 250b63bc124..0d3766f1a33 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfoOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupInfoOrBuilder.java @@ -30,7 +30,7 @@ public interface BackupInfoOrBuilder * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The backup. */ @@ -42,7 +42,7 @@ public interface BackupInfoOrBuilder * Name of the backup. * * - * string backup = 1; + * string backup = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for backup. */ @@ -53,7 +53,54 @@ public interface BackupInfoOrBuilder * *
        * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return Whether the versionTime field is set. + */ + boolean hasVersionTime(); + /** + * + * + *
    +   * The backup contains an externally consistent copy of `source_database` at
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + * + * @return The versionTime. + */ + com.google.protobuf.Timestamp getVersionTime(); + /** + * + * + *
    +   * The backup contains an externally consistent copy of `source_database` at
    +   * the timestamp specified by `version_time`. If the
    +   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify
    +   * `version_time`, the `version_time` of the backup is equivalent to the
    +   * `create_time`.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 4; + */ + com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder(); + + /** + * + * + *
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -65,8 +112,8 @@ public interface BackupInfoOrBuilder * * *
    -   * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -78,8 +125,8 @@ public interface BackupInfoOrBuilder * * *
    -   * The backup contains an externally consistent copy of `source_database` at
    -   * the timestamp specified by `create_time`.
    +   * The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was
    +   * received.
        * 
    * * .google.protobuf.Timestamp create_time = 2; @@ -93,7 +140,7 @@ public interface BackupInfoOrBuilder * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The sourceDatabase. */ @@ -105,7 +152,7 @@ public interface BackupInfoOrBuilder * Name of the database the backup was created from. * * - * string source_database = 3; + * string source_database = 3 [(.google.api.resource_reference) = { ... } * * @return The bytes for sourceDatabase. */ diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupOrBuilder.java index 2b7f566b122..67cfab26fc9 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupOrBuilder.java @@ -56,6 +56,50 @@ public interface BackupOrBuilder */ com.google.protobuf.ByteString getDatabaseBytes(); + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return Whether the versionTime field is set. + */ + boolean hasVersionTime(); + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + * + * @return The versionTime. + */ + com.google.protobuf.Timestamp getVersionTime(); + /** + * + * + *
    +   * The backup will contain an externally consistent copy of the database at
    +   * the timestamp specified by `version_time`. If `version_time` is not
    +   * specified, the system will set `version_time` to the `create_time` of the
    +   * backup.
    +   * 
    + * + * .google.protobuf.Timestamp version_time = 9; + */ + com.google.protobuf.TimestampOrBuilder getVersionTimeOrBuilder(); + /** * * @@ -155,10 +199,9 @@ public interface BackupOrBuilder * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; @@ -171,10 +214,9 @@ public interface BackupOrBuilder * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; @@ -187,10 +229,9 @@ public interface BackupOrBuilder * * *
    -   * Output only. The backup will contain an externally consistent
    -   * copy of the database at the timestamp specified by
    -   * `create_time`. `create_time` is approximately the time the
    -   * [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received.
    +   * Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]
    +   * request is received. If the request does not specify `version_time`, the
    +   * `version_time` of the backup will be equivalent to the `create_time`.
        * 
    * * .google.protobuf.Timestamp create_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupProto.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupProto.java index 2aaecf7bcd8..dd28586f35e 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupProto.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/BackupProto.java @@ -88,56 +88,62 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + "d_mask.proto\032\037google/protobuf/timestamp." + "proto\032-google/spanner/admin/database/v1/" + "common.proto\032\034google/api/annotations.pro" - + "to\"\315\003\n\006Backup\0226\n\010database\030\002 \001(\tB$\372A!\n\037sp" - + "anner.googleapis.com/Database\022/\n\013expire_" - + "time\030\003 \001(\0132\032.google.protobuf.Timestamp\022\014" - + "\n\004name\030\001 \001(\t\0224\n\013create_time\030\004 \001(\0132\032.goog" - + "le.protobuf.TimestampB\003\340A\003\022\027\n\nsize_bytes" - + "\030\005 \001(\003B\003\340A\003\022B\n\005state\030\006 \001(\0162..google.span" - + "ner.admin.database.v1.Backup.StateB\003\340A\003\022" - + "\"\n\025referencing_databases\030\007 \003(\tB\003\340A\003\"7\n\005S" - + "tate\022\025\n\021STATE_UNSPECIFIED\020\000\022\014\n\010CREATING\020" - + "\001\022\t\n\005READY\020\002:\\\352AY\n\035spanner.googleapis.co" - + "m/Backup\0228projects/{project}/instances/{" - + "instance}/backups/{backup}\"\245\001\n\023CreateBac" - + "kupRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037span" - + "ner.googleapis.com/Instance\022\026\n\tbackup_id" - + "\030\002 \001(\tB\003\340A\002\022=\n\006backup\030\003 \001(\0132(.google.spa" - + "nner.admin.database.v1.BackupB\003\340A\002\"\256\001\n\024C" - + "reateBackupMetadata\022\014\n\004name\030\001 \001(\t\022\020\n\010dat" - + "abase\030\002 \001(\t\022E\n\010progress\030\003 \001(\01323.google.s" - + "panner.admin.database.v1.OperationProgre" - + "ss\022/\n\013cancel_time\030\004 \001(\0132\032.google.protobu" - + "f.Timestamp\"\212\001\n\023UpdateBackupRequest\022=\n\006b" - + "ackup\030\001 \001(\0132(.google.spanner.admin.datab" - + "ase.v1.BackupB\003\340A\002\0224\n\013update_mask\030\002 \001(\0132" - + "\032.google.protobuf.FieldMaskB\003\340A\002\"G\n\020GetB" + + "to\"\377\003\n\006Backup\0226\n\010database\030\002 \001(\tB$\372A!\n\037sp" + + "anner.googleapis.com/Database\0220\n\014version" + + "_time\030\t \001(\0132\032.google.protobuf.Timestamp\022" + + "/\n\013expire_time\030\003 \001(\0132\032.google.protobuf.T" + + "imestamp\022\014\n\004name\030\001 \001(\t\0224\n\013create_time\030\004 " + + "\001(\0132\032.google.protobuf.TimestampB\003\340A\003\022\027\n\n" + + "size_bytes\030\005 \001(\003B\003\340A\003\022B\n\005state\030\006 \001(\0162..g" + + "oogle.spanner.admin.database.v1.Backup.S" + + "tateB\003\340A\003\022\"\n\025referencing_databases\030\007 \003(\t" + + "B\003\340A\003\"7\n\005State\022\025\n\021STATE_UNSPECIFIED\020\000\022\014\n" + + "\010CREATING\020\001\022\t\n\005READY\020\002:\\\352AY\n\035spanner.goo" + + "gleapis.com/Backup\0228projects/{project}/i" + + "nstances/{instance}/backups/{backup}\"\245\001\n" + + "\023CreateBackupRequest\0227\n\006parent\030\001 \001(\tB\'\340A" + + "\002\372A!\n\037spanner.googleapis.com/Instance\022\026\n" + + "\tbackup_id\030\002 \001(\tB\003\340A\002\022=\n\006backup\030\003 \001(\0132(." + + "google.spanner.admin.database.v1.BackupB" + + "\003\340A\002\"\370\001\n\024CreateBackupMetadata\0220\n\004name\030\001 " + + "\001(\tB\"\372A\037\n\035spanner.googleapis.com/Backup\022" + + "6\n\010database\030\002 \001(\tB$\372A!\n\037spanner.googleap" + + "is.com/Database\022E\n\010progress\030\003 \001(\01323.goog" + + "le.spanner.admin.database.v1.OperationPr" + + "ogress\022/\n\013cancel_time\030\004 \001(\0132\032.google.pro" + + "tobuf.Timestamp\"\212\001\n\023UpdateBackupRequest\022" + + "=\n\006backup\030\001 \001(\0132(.google.spanner.admin.d" + + "atabase.v1.BackupB\003\340A\002\0224\n\013update_mask\030\002 " + + "\001(\0132\032.google.protobuf.FieldMaskB\003\340A\002\"G\n\020" + + "GetBackupRequest\0223\n\004name\030\001 \001(\tB%\340A\002\372A\037\n\035" + + "spanner.googleapis.com/Backup\"J\n\023DeleteB" + "ackupRequest\0223\n\004name\030\001 \001(\tB%\340A\002\372A\037\n\035span" - + "ner.googleapis.com/Backup\"J\n\023DeleteBacku" - + "pRequest\0223\n\004name\030\001 \001(\tB%\340A\002\372A\037\n\035spanner." - + "googleapis.com/Backup\"\204\001\n\022ListBackupsReq" - + "uest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.go" - + "ogleapis.com/Instance\022\016\n\006filter\030\002 \001(\t\022\021\n" - + "\tpage_size\030\003 \001(\005\022\022\n\npage_token\030\004 \001(\t\"i\n\023" - + "ListBackupsResponse\0229\n\007backups\030\001 \003(\0132(.g" - + "oogle.spanner.admin.database.v1.Backup\022\027" - + "\n\017next_page_token\030\002 \001(\t\"\215\001\n\033ListBackupOp" - + "erationsRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n" - + "\037spanner.googleapis.com/Instance\022\016\n\006filt" - + "er\030\002 \001(\t\022\021\n\tpage_size\030\003 \001(\005\022\022\n\npage_toke" - + "n\030\004 \001(\t\"j\n\034ListBackupOperationsResponse\022" - + "1\n\noperations\030\001 \003(\0132\035.google.longrunning" - + ".Operation\022\027\n\017next_page_token\030\002 \001(\t\"f\n\nB" - + "ackupInfo\022\016\n\006backup\030\001 \001(\t\022/\n\013create_time" - + "\030\002 \001(\0132\032.google.protobuf.Timestamp\022\027\n\017so" - + "urce_database\030\003 \001(\tB\377\001\n$com.google.spann" - + "er.admin.database.v1B\013BackupProtoP\001ZHgoo" - + "gle.golang.org/genproto/googleapis/spann" - + "er/admin/database/v1;database\252\002&Google.C" - + "loud.Spanner.Admin.Database.V1\312\002&Google\\" - + "Cloud\\Spanner\\Admin\\Database\\V1\352\002+Google" - + "::Cloud::Spanner::Admin::Database::V1b\006p" - + "roto3" + + "ner.googleapis.com/Backup\"\204\001\n\022ListBackup" + + "sRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037spanne" + + "r.googleapis.com/Instance\022\016\n\006filter\030\002 \001(" + + "\t\022\021\n\tpage_size\030\003 \001(\005\022\022\n\npage_token\030\004 \001(\t" + + "\"i\n\023ListBackupsResponse\0229\n\007backups\030\001 \003(\013" + + "2(.google.spanner.admin.database.v1.Back" + + "up\022\027\n\017next_page_token\030\002 \001(\t\"\215\001\n\033ListBack" + + "upOperationsRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002" + + "\372A!\n\037spanner.googleapis.com/Instance\022\016\n\006" + + "filter\030\002 \001(\t\022\021\n\tpage_size\030\003 \001(\005\022\022\n\npage_" + + "token\030\004 \001(\t\"j\n\034ListBackupOperationsRespo" + + "nse\0221\n\noperations\030\001 \003(\0132\035.google.longrun" + + "ning.Operation\022\027\n\017next_page_token\030\002 \001(\t\"" + + "\342\001\n\nBackupInfo\0222\n\006backup\030\001 \001(\tB\"\372A\037\n\035spa" + + "nner.googleapis.com/Backup\0220\n\014version_ti" + + "me\030\004 \001(\0132\032.google.protobuf.Timestamp\022/\n\013" + + "create_time\030\002 \001(\0132\032.google.protobuf.Time" + + "stamp\022=\n\017source_database\030\003 \001(\tB$\372A!\n\037spa" + + "nner.googleapis.com/DatabaseB\377\001\n$com.goo" + + "gle.spanner.admin.database.v1B\013BackupPro" + + "toP\001ZHgoogle.golang.org/genproto/googlea" + + "pis/spanner/admin/database/v1;database\252\002" + + "&Google.Cloud.Spanner.Admin.Database.V1\312" + + "\002&Google\\Cloud\\Spanner\\Admin\\Database\\V1" + + "\352\002+Google::Cloud::Spanner::Admin::Databa" + + "se::V1b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( @@ -158,6 +164,7 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { internal_static_google_spanner_admin_database_v1_Backup_descriptor, new java.lang.String[] { "Database", + "VersionTime", "ExpireTime", "Name", "CreateTime", @@ -243,7 +250,7 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_google_spanner_admin_database_v1_BackupInfo_descriptor, new java.lang.String[] { - "Backup", "CreateTime", "SourceDatabase", + "Backup", "VersionTime", "CreateTime", "SourceDatabase", }); com.google.protobuf.ExtensionRegistry registry = com.google.protobuf.ExtensionRegistry.newInstance(); diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadata.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadata.java index a1b0cc35124..c4e23c96e69 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadata.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadata.java @@ -161,7 +161,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -184,7 +184,7 @@ public java.lang.String getName() { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -210,7 +210,7 @@ public com.google.protobuf.ByteString getNameBytes() { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The database. */ @@ -233,7 +233,7 @@ public java.lang.String getDatabase() { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The bytes for database. */ @@ -777,7 +777,7 @@ public Builder mergeFrom( * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -799,7 +799,7 @@ public java.lang.String getName() { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -821,7 +821,7 @@ public com.google.protobuf.ByteString getNameBytes() { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The name to set. * @return This builder for chaining. @@ -842,7 +842,7 @@ public Builder setName(java.lang.String value) { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -859,7 +859,7 @@ public Builder clearName() { * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The bytes for name to set. * @return This builder for chaining. @@ -883,7 +883,7 @@ public Builder setNameBytes(com.google.protobuf.ByteString value) { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The database. */ @@ -905,7 +905,7 @@ public java.lang.String getDatabase() { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The bytes for database. */ @@ -927,7 +927,7 @@ public com.google.protobuf.ByteString getDatabaseBytes() { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @param value The database to set. * @return This builder for chaining. @@ -948,7 +948,7 @@ public Builder setDatabase(java.lang.String value) { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -965,7 +965,7 @@ public Builder clearDatabase() { * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @param value The bytes for database to set. * @return This builder for chaining. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadataOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadataOrBuilder.java index 5034e3af02d..fcb89c03472 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadataOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/CreateBackupMetadataOrBuilder.java @@ -30,7 +30,7 @@ public interface CreateBackupMetadataOrBuilder * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -42,7 +42,7 @@ public interface CreateBackupMetadataOrBuilder * The name of the backup being created. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -55,7 +55,7 @@ public interface CreateBackupMetadataOrBuilder * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The database. */ @@ -67,7 +67,7 @@ public interface CreateBackupMetadataOrBuilder * The name of the database the backup is created from. * * - * string database = 2; + * string database = 2 [(.google.api.resource_reference) = { ... } * * @return The bytes for database. */ diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Database.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Database.java index 768f470254d..f4c866c8f85 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Database.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/Database.java @@ -40,6 +40,7 @@ private Database(com.google.protobuf.GeneratedMessageV3.Builder builder) { private Database() { name_ = ""; state_ = 0; + versionRetentionPeriod_ = ""; } @java.lang.Override @@ -114,6 +115,28 @@ private Database( restoreInfo_ = subBuilder.buildPartial(); } + break; + } + case 50: + { + java.lang.String s = input.readStringRequireUtf8(); + + versionRetentionPeriod_ = s; + break; + } + case 58: + { + com.google.protobuf.Timestamp.Builder subBuilder = null; + if (earliestVersionTime_ != null) { + subBuilder = earliestVersionTime_.toBuilder(); + } + earliestVersionTime_ = + input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(earliestVersionTime_); + earliestVersionTime_ = subBuilder.buildPartial(); + } + break; } default: @@ -548,6 +571,120 @@ public com.google.spanner.admin.database.v1.RestoreInfoOrBuilder getRestoreInfoO return getRestoreInfo(); } + public static final int VERSION_RETENTION_PERIOD_FIELD_NUMBER = 6; + private volatile java.lang.Object versionRetentionPeriod_; + /** + * + * + *
    +   * Output only. The period in which Cloud Spanner retains all versions of data
    +   * for the database. This is the same as the value of version_retention_period
    +   * database option set using
    +   * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +   * if not set.
    +   * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * @return The versionRetentionPeriod. + */ + @java.lang.Override + public java.lang.String getVersionRetentionPeriod() { + java.lang.Object ref = versionRetentionPeriod_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + versionRetentionPeriod_ = s; + return s; + } + } + /** + * + * + *
    +   * Output only. The period in which Cloud Spanner retains all versions of data
    +   * for the database. This is the same as the value of version_retention_period
    +   * database option set using
    +   * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +   * if not set.
    +   * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * @return The bytes for versionRetentionPeriod. + */ + @java.lang.Override + public com.google.protobuf.ByteString getVersionRetentionPeriodBytes() { + java.lang.Object ref = versionRetentionPeriod_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + versionRetentionPeriod_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int EARLIEST_VERSION_TIME_FIELD_NUMBER = 7; + private com.google.protobuf.Timestamp earliestVersionTime_; + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return Whether the earliestVersionTime field is set. + */ + @java.lang.Override + public boolean hasEarliestVersionTime() { + return earliestVersionTime_ != null; + } + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return The earliestVersionTime. + */ + @java.lang.Override + public com.google.protobuf.Timestamp getEarliestVersionTime() { + return earliestVersionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : earliestVersionTime_; + } + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + @java.lang.Override + public com.google.protobuf.TimestampOrBuilder getEarliestVersionTimeOrBuilder() { + return getEarliestVersionTime(); + } + private byte memoizedIsInitialized = -1; @java.lang.Override @@ -575,6 +712,12 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io if (restoreInfo_ != null) { output.writeMessage(4, getRestoreInfo()); } + if (!getVersionRetentionPeriodBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 6, versionRetentionPeriod_); + } + if (earliestVersionTime_ != null) { + output.writeMessage(7, getEarliestVersionTime()); + } unknownFields.writeTo(output); } @@ -597,6 +740,12 @@ public int getSerializedSize() { if (restoreInfo_ != null) { size += com.google.protobuf.CodedOutputStream.computeMessageSize(4, getRestoreInfo()); } + if (!getVersionRetentionPeriodBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, versionRetentionPeriod_); + } + if (earliestVersionTime_ != null) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(7, getEarliestVersionTime()); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -623,6 +772,11 @@ public boolean equals(final java.lang.Object obj) { if (hasRestoreInfo()) { if (!getRestoreInfo().equals(other.getRestoreInfo())) return false; } + if (!getVersionRetentionPeriod().equals(other.getVersionRetentionPeriod())) return false; + if (hasEarliestVersionTime() != other.hasEarliestVersionTime()) return false; + if (hasEarliestVersionTime()) { + if (!getEarliestVersionTime().equals(other.getEarliestVersionTime())) return false; + } if (!unknownFields.equals(other.unknownFields)) return false; return true; } @@ -646,6 +800,12 @@ public int hashCode() { hash = (37 * hash) + RESTORE_INFO_FIELD_NUMBER; hash = (53 * hash) + getRestoreInfo().hashCode(); } + hash = (37 * hash) + VERSION_RETENTION_PERIOD_FIELD_NUMBER; + hash = (53 * hash) + getVersionRetentionPeriod().hashCode(); + if (hasEarliestVersionTime()) { + hash = (37 * hash) + EARLIEST_VERSION_TIME_FIELD_NUMBER; + hash = (53 * hash) + getEarliestVersionTime().hashCode(); + } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -807,6 +967,14 @@ public Builder clear() { restoreInfo_ = null; restoreInfoBuilder_ = null; } + versionRetentionPeriod_ = ""; + + if (earliestVersionTimeBuilder_ == null) { + earliestVersionTime_ = null; + } else { + earliestVersionTime_ = null; + earliestVersionTimeBuilder_ = null; + } return this; } @@ -846,6 +1014,12 @@ public com.google.spanner.admin.database.v1.Database buildPartial() { } else { result.restoreInfo_ = restoreInfoBuilder_.build(); } + result.versionRetentionPeriod_ = versionRetentionPeriod_; + if (earliestVersionTimeBuilder_ == null) { + result.earliestVersionTime_ = earliestVersionTime_; + } else { + result.earliestVersionTime_ = earliestVersionTimeBuilder_.build(); + } onBuilt(); return result; } @@ -908,6 +1082,13 @@ public Builder mergeFrom(com.google.spanner.admin.database.v1.Database other) { if (other.hasRestoreInfo()) { mergeRestoreInfo(other.getRestoreInfo()); } + if (!other.getVersionRetentionPeriod().isEmpty()) { + versionRetentionPeriod_ = other.versionRetentionPeriod_; + onChanged(); + } + if (other.hasEarliestVersionTime()) { + mergeEarliestVersionTime(other.getEarliestVersionTime()); + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -1580,6 +1761,349 @@ public com.google.spanner.admin.database.v1.RestoreInfoOrBuilder getRestoreInfoO return restoreInfoBuilder_; } + private java.lang.Object versionRetentionPeriod_ = ""; + /** + * + * + *
    +     * Output only. The period in which Cloud Spanner retains all versions of data
    +     * for the database. This is the same as the value of version_retention_period
    +     * database option set using
    +     * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +     * if not set.
    +     * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return The versionRetentionPeriod. + */ + public java.lang.String getVersionRetentionPeriod() { + java.lang.Object ref = versionRetentionPeriod_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + versionRetentionPeriod_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * + * + *
    +     * Output only. The period in which Cloud Spanner retains all versions of data
    +     * for the database. This is the same as the value of version_retention_period
    +     * database option set using
    +     * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +     * if not set.
    +     * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return The bytes for versionRetentionPeriod. + */ + public com.google.protobuf.ByteString getVersionRetentionPeriodBytes() { + java.lang.Object ref = versionRetentionPeriod_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + versionRetentionPeriod_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * + * + *
    +     * Output only. The period in which Cloud Spanner retains all versions of data
    +     * for the database. This is the same as the value of version_retention_period
    +     * database option set using
    +     * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +     * if not set.
    +     * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @param value The versionRetentionPeriod to set. + * @return This builder for chaining. + */ + public Builder setVersionRetentionPeriod(java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + versionRetentionPeriod_ = value; + onChanged(); + return this; + } + /** + * + * + *
    +     * Output only. The period in which Cloud Spanner retains all versions of data
    +     * for the database. This is the same as the value of version_retention_period
    +     * database option set using
    +     * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +     * if not set.
    +     * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return This builder for chaining. + */ + public Builder clearVersionRetentionPeriod() { + + versionRetentionPeriod_ = getDefaultInstance().getVersionRetentionPeriod(); + onChanged(); + return this; + } + /** + * + * + *
    +     * Output only. The period in which Cloud Spanner retains all versions of data
    +     * for the database. This is the same as the value of version_retention_period
    +     * database option set using
    +     * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +     * if not set.
    +     * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @param value The bytes for versionRetentionPeriod to set. + * @return This builder for chaining. + */ + public Builder setVersionRetentionPeriodBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + versionRetentionPeriod_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.Timestamp earliestVersionTime_; + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + earliestVersionTimeBuilder_; + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return Whether the earliestVersionTime field is set. + */ + public boolean hasEarliestVersionTime() { + return earliestVersionTimeBuilder_ != null || earliestVersionTime_ != null; + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return The earliestVersionTime. + */ + public com.google.protobuf.Timestamp getEarliestVersionTime() { + if (earliestVersionTimeBuilder_ == null) { + return earliestVersionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : earliestVersionTime_; + } else { + return earliestVersionTimeBuilder_.getMessage(); + } + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public Builder setEarliestVersionTime(com.google.protobuf.Timestamp value) { + if (earliestVersionTimeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + earliestVersionTime_ = value; + onChanged(); + } else { + earliestVersionTimeBuilder_.setMessage(value); + } + + return this; + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public Builder setEarliestVersionTime(com.google.protobuf.Timestamp.Builder builderForValue) { + if (earliestVersionTimeBuilder_ == null) { + earliestVersionTime_ = builderForValue.build(); + onChanged(); + } else { + earliestVersionTimeBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public Builder mergeEarliestVersionTime(com.google.protobuf.Timestamp value) { + if (earliestVersionTimeBuilder_ == null) { + if (earliestVersionTime_ != null) { + earliestVersionTime_ = + com.google.protobuf.Timestamp.newBuilder(earliestVersionTime_) + .mergeFrom(value) + .buildPartial(); + } else { + earliestVersionTime_ = value; + } + onChanged(); + } else { + earliestVersionTimeBuilder_.mergeFrom(value); + } + + return this; + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public Builder clearEarliestVersionTime() { + if (earliestVersionTimeBuilder_ == null) { + earliestVersionTime_ = null; + onChanged(); + } else { + earliestVersionTime_ = null; + earliestVersionTimeBuilder_ = null; + } + + return this; + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public com.google.protobuf.Timestamp.Builder getEarliestVersionTimeBuilder() { + + onChanged(); + return getEarliestVersionTimeFieldBuilder().getBuilder(); + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + public com.google.protobuf.TimestampOrBuilder getEarliestVersionTimeOrBuilder() { + if (earliestVersionTimeBuilder_ != null) { + return earliestVersionTimeBuilder_.getMessageOrBuilder(); + } else { + return earliestVersionTime_ == null + ? com.google.protobuf.Timestamp.getDefaultInstance() + : earliestVersionTime_; + } + } + /** + * + * + *
    +     * Output only. Earliest timestamp at which older versions of the data can be
    +     * read.
    +     * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder> + getEarliestVersionTimeFieldBuilder() { + if (earliestVersionTimeBuilder_ == null) { + earliestVersionTimeBuilder_ = + new com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Timestamp, + com.google.protobuf.Timestamp.Builder, + com.google.protobuf.TimestampOrBuilder>( + getEarliestVersionTime(), getParentForChildren(), isClean()); + earliestVersionTime_ = null; + } + return earliestVersionTimeBuilder_; + } + @java.lang.Override public final Builder setUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/DatabaseOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/DatabaseOrBuilder.java index 4c4869319f2..9ca843fc93f 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/DatabaseOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/DatabaseOrBuilder.java @@ -166,4 +166,81 @@ public interface DatabaseOrBuilder *
    */ com.google.spanner.admin.database.v1.RestoreInfoOrBuilder getRestoreInfoOrBuilder(); + + /** + * + * + *
    +   * Output only. The period in which Cloud Spanner retains all versions of data
    +   * for the database. This is the same as the value of version_retention_period
    +   * database option set using
    +   * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +   * if not set.
    +   * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * @return The versionRetentionPeriod. + */ + java.lang.String getVersionRetentionPeriod(); + /** + * + * + *
    +   * Output only. The period in which Cloud Spanner retains all versions of data
    +   * for the database. This is the same as the value of version_retention_period
    +   * database option set using
    +   * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour,
    +   * if not set.
    +   * 
    + * + * string version_retention_period = 6 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * @return The bytes for versionRetentionPeriod. + */ + com.google.protobuf.ByteString getVersionRetentionPeriodBytes(); + + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return Whether the earliestVersionTime field is set. + */ + boolean hasEarliestVersionTime(); + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + * + * @return The earliestVersionTime. + */ + com.google.protobuf.Timestamp getEarliestVersionTime(); + /** + * + * + *
    +   * Output only. Earliest timestamp at which older versions of the data can be
    +   * read.
    +   * 
    + * + * + * .google.protobuf.Timestamp earliest_version_time = 7 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * + */ + com.google.protobuf.TimestampOrBuilder getEarliestVersionTimeOrBuilder(); } diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequest.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequest.java index d730718fb37..f5bc73b7d83 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequest.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequest.java @@ -118,6 +118,8 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * *
        * Required. The database whose schema we wish to get.
    +   * Values are of the form
    +   * `projects/<project>/instances/<instance>/databases/<database>`
        * 
    * * @@ -143,6 +145,8 @@ public java.lang.String getDatabase() { * *
        * Required. The database whose schema we wish to get.
    +   * Values are of the form
    +   * `projects/<project>/instances/<instance>/databases/<database>`
        * 
    * * @@ -489,6 +493,8 @@ public Builder mergeFrom( * *
          * Required. The database whose schema we wish to get.
    +     * Values are of the form
    +     * `projects/<project>/instances/<instance>/databases/<database>`
          * 
    * * @@ -513,6 +519,8 @@ public java.lang.String getDatabase() { * *
          * Required. The database whose schema we wish to get.
    +     * Values are of the form
    +     * `projects/<project>/instances/<instance>/databases/<database>`
          * 
    * * @@ -537,6 +545,8 @@ public com.google.protobuf.ByteString getDatabaseBytes() { * *
          * Required. The database whose schema we wish to get.
    +     * Values are of the form
    +     * `projects/<project>/instances/<instance>/databases/<database>`
          * 
    * * @@ -560,6 +570,8 @@ public Builder setDatabase(java.lang.String value) { * *
          * Required. The database whose schema we wish to get.
    +     * Values are of the form
    +     * `projects/<project>/instances/<instance>/databases/<database>`
          * 
    * * @@ -579,6 +591,8 @@ public Builder clearDatabase() { * *
          * Required. The database whose schema we wish to get.
    +     * Values are of the form
    +     * `projects/<project>/instances/<instance>/databases/<database>`
          * 
    * * diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequestOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequestOrBuilder.java index 2e7da9cf5a4..2469460dc3e 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequestOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/GetDatabaseDdlRequestOrBuilder.java @@ -28,6 +28,8 @@ public interface GetDatabaseDdlRequestOrBuilder * *
        * Required. The database whose schema we wish to get.
    +   * Values are of the form
    +   * `projects/<project>/instances/<instance>/databases/<database>`
        * 
    * * @@ -42,6 +44,8 @@ public interface GetDatabaseDdlRequestOrBuilder * *
        * Required. The database whose schema we wish to get.
    +   * Values are of the form
    +   * `projects/<project>/instances/<instance>/databases/<database>`
        * 
    * * diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequest.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequest.java index baea7c8f1ff..a49ee408beb 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequest.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequest.java @@ -218,9 +218,9 @@ public com.google.protobuf.ByteString getParentBytes() { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -272,9 +272,9 @@ public java.lang.String getFilter() { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -894,9 +894,9 @@ public Builder setParentBytes(com.google.protobuf.ByteString value) { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -947,9 +947,9 @@ public java.lang.String getFilter() { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -1000,9 +1000,9 @@ public com.google.protobuf.ByteString getFilterBytes() { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -1052,9 +1052,9 @@ public Builder setFilter(java.lang.String value) { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -1100,9 +1100,9 @@ public Builder clearFilter() { * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequestOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequestOrBuilder.java index 4f43eba2884..6d48d7f5f3f 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequestOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListBackupOperationsRequestOrBuilder.java @@ -82,9 +82,9 @@ public interface ListBackupOperationsRequestOrBuilder * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". @@ -125,9 +125,9 @@ public interface ListBackupOperationsRequestOrBuilder * * `done:true` - The operation is complete. * * `metadata.database:prod` - The database the backup was taken from has * a name containing the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` <br/> - * `(metadata.name:howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + * `(metadata.name:howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. * * The backup name contains the string "howl". diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequest.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequest.java index 06cff39b688..d59caa1f7de 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequest.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequest.java @@ -216,11 +216,11 @@ public com.google.protobuf.ByteString getParentBytes() { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -272,11 +272,11 @@ public java.lang.String getFilter() { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -900,11 +900,11 @@ public Builder setParentBytes(com.google.protobuf.ByteString value) { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -955,11 +955,11 @@ public java.lang.String getFilter() { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -1010,11 +1010,11 @@ public com.google.protobuf.ByteString getFilterBytes() { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -1064,11 +1064,11 @@ public Builder setFilter(java.lang.String value) { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -1114,11 +1114,11 @@ public Builder clearFilter() { * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequestOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequestOrBuilder.java index 115dcd7551a..38eb5693e1a 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequestOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/ListDatabaseOperationsRequestOrBuilder.java @@ -80,11 +80,11 @@ public interface ListDatabaseOperationsRequestOrBuilder * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. @@ -125,11 +125,11 @@ public interface ListDatabaseOperationsRequestOrBuilder * you can specify AND, OR, and NOT logic explicitly. * Here are a few examples: * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` <br/> - * `(metadata.source_type:BACKUP) AND` <br/> - * `(metadata.backup_info.backup:backup_howl) AND` <br/> - * `(metadata.name:restored_howl) AND` <br/> - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` <br/> + * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + * `(metadata.source_type:BACKUP) AND` \ + * `(metadata.backup_info.backup:backup_howl) AND` \ + * `(metadata.name:restored_howl) AND` \ + * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: * * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. * * The database is restored from a backup. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadata.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadata.java index 88c49cdc159..6bdf68bc71c 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadata.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadata.java @@ -141,7 +141,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -164,7 +164,7 @@ public java.lang.String getName() { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -595,7 +595,7 @@ public Builder mergeFrom( * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -617,7 +617,7 @@ public java.lang.String getName() { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -639,7 +639,7 @@ public com.google.protobuf.ByteString getNameBytes() { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The name to set. * @return This builder for chaining. @@ -660,7 +660,7 @@ public Builder setName(java.lang.String value) { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -677,7 +677,7 @@ public Builder clearName() { * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The bytes for name to set. * @return This builder for chaining. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadataOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadataOrBuilder.java index a5a211d446b..0606518a011 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadataOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/OptimizeRestoredDatabaseMetadataOrBuilder.java @@ -30,7 +30,7 @@ public interface OptimizeRestoredDatabaseMetadataOrBuilder * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -42,7 +42,7 @@ public interface OptimizeRestoredDatabaseMetadataOrBuilder * Name of the restored database being optimized. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadata.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadata.java index 4cb99151034..8a849d56711 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadata.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadata.java @@ -230,7 +230,7 @@ public SourceInfoCase getSourceInfoCase() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -253,7 +253,7 @@ public java.lang.String getName() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -1039,7 +1039,7 @@ public Builder clearSourceInfo() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -1061,7 +1061,7 @@ public java.lang.String getName() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ @@ -1083,7 +1083,7 @@ public com.google.protobuf.ByteString getNameBytes() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The name to set. * @return This builder for chaining. @@ -1104,7 +1104,7 @@ public Builder setName(java.lang.String value) { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return This builder for chaining. */ @@ -1121,7 +1121,7 @@ public Builder clearName() { * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @param value The bytes for name to set. * @return This builder for chaining. diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadataOrBuilder.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadataOrBuilder.java index 8d05e8b6b7c..35ad6d76e9d 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadataOrBuilder.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/RestoreDatabaseMetadataOrBuilder.java @@ -30,7 +30,7 @@ public interface RestoreDatabaseMetadataOrBuilder * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The name. */ @@ -42,7 +42,7 @@ public interface RestoreDatabaseMetadataOrBuilder * Name of the database being created and restored to. * * - * string name = 1; + * string name = 1 [(.google.api.resource_reference) = { ... } * * @return The bytes for name. */ diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/SpannerDatabaseAdminProto.java b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/SpannerDatabaseAdminProto.java index 032a813e29b..7b981be4f45 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/SpannerDatabaseAdminProto.java +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/java/com/google/spanner/admin/database/v1/SpannerDatabaseAdminProto.java @@ -120,176 +120,181 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + "admin.database.v1.RestoreSourceType\022C\n\013b" + "ackup_info\030\002 \001(\0132,.google.spanner.admin." + "database.v1.BackupInfoH\000B\r\n\013source_info\"" - + "\226\003\n\010Database\022\021\n\004name\030\001 \001(\tB\003\340A\002\022D\n\005state" + + "\375\003\n\010Database\022\021\n\004name\030\001 \001(\tB\003\340A\002\022D\n\005state" + "\030\002 \001(\01620.google.spanner.admin.database.v" + "1.Database.StateB\003\340A\003\0224\n\013create_time\030\003 \001" + "(\0132\032.google.protobuf.TimestampB\003\340A\003\022H\n\014r" + "estore_info\030\004 \001(\0132-.google.spanner.admin" - + ".database.v1.RestoreInfoB\003\340A\003\"M\n\005State\022\025" - + "\n\021STATE_UNSPECIFIED\020\000\022\014\n\010CREATING\020\001\022\t\n\005R" - + "EADY\020\002\022\024\n\020READY_OPTIMIZING\020\003:b\352A_\n\037spann" - + "er.googleapis.com/Database\022\202\323\344\223" - + "\002/\022-/v1/{parent=projects/*/instances/*}/" - + "databases\332A\006parent\022\244\002\n\016CreateDatabase\0227." - + "google.spanner.admin.database.v1.CreateD" - + "atabaseRequest\032\035.google.longrunning.Oper" - + "ation\"\271\001\202\323\344\223\0022\"-/v1/{parent=projects/*/i" - + "nstances/*}/databases:\001*\332A\027parent,create" - + "_statement\312Ad\n)google.spanner.admin.data" - + "base.v1.Database\0227google.spanner.admin.d" - + "atabase.v1.CreateDatabaseMetadata\022\255\001\n\013Ge" - + "tDatabase\0224.google.spanner.admin.databas" - + "e.v1.GetDatabaseRequest\032*.google.spanner" - + ".admin.database.v1.Database\"<\202\323\344\223\002/\022-/v1" - + "/{name=projects/*/instances/*/databases/" - + "*}\332A\004name\022\235\002\n\021UpdateDatabaseDdl\022:.google" - + ".spanner.admin.database.v1.UpdateDatabas" - + "eDdlRequest\032\035.google.longrunning.Operati" - + "on\"\254\001\202\323\344\223\002:25/v1/{database=projects/*/in" - + "stances/*/databases/*}/ddl:\001*\332A\023database" - + ",statements\312AS\n\025google.protobuf.Empty\022:g" - + "oogle.spanner.admin.database.v1.UpdateDa" - + "tabaseDdlMetadata\022\243\001\n\014DropDatabase\0225.goo" - + "gle.spanner.admin.database.v1.DropDataba" - + "seRequest\032\026.google.protobuf.Empty\"D\202\323\344\223\002" - + "3*1/v1/{database=projects/*/instances/*/" - + "databases/*}\332A\010database\022\315\001\n\016GetDatabaseD" - + "dl\0227.google.spanner.admin.database.v1.Ge" - + "tDatabaseDdlRequest\0328.google.spanner.adm" - + "in.database.v1.GetDatabaseDdlResponse\"H\202" - + "\323\344\223\0027\0225/v1/{database=projects/*/instance" - + "s/*/databases/*}/ddl\332A\010database\022\353\001\n\014SetI" - + "amPolicy\022\".google.iam.v1.SetIamPolicyReq" - + "uest\032\025.google.iam.v1.Policy\"\237\001\202\323\344\223\002\206\001\">/" - + "v1/{resource=projects/*/instances/*/data" - + "bases/*}:setIamPolicy:\001*ZA\"/v1" - + "/{resource=projects/*/instances/*/databa" - + "ses/*}:getIamPolicy:\001*ZA\".google.spanner.admi" - + "n.database.v1.ListBackupOperationsRespon" - + "se\"E\202\323\344\223\0026\0224/v1/{parent=projects/*/insta" - + "nces/*}/backupOperations\332A\006parent\032x\312A\026sp" - + "anner.googleapis.com\322A\\https://www.googl" - + "eapis.com/auth/cloud-platform,https://ww" - + "w.googleapis.com/auth/spanner.adminB\332\002\n$" - + "com.google.spanner.admin.database.v1B\031Sp" - + "annerDatabaseAdminProtoP\001ZHgoogle.golang" - + ".org/genproto/googleapis/spanner/admin/d" - + "atabase/v1;database\252\002&Google.Cloud.Spann" - + "er.Admin.Database.V1\312\002&Google\\Cloud\\Span" - + "ner\\Admin\\Database\\V1\352\002+Google::Cloud::S" - + "panner::Admin::Database::V1\352AJ\n\037spanner." - + "googleapis.com/Instance\022\'projects/{proje" - + "ct}/instances/{instance}b\006proto3" + + ".database.v1.RestoreInfoB\003\340A\003\022%\n\030version" + + "_retention_period\030\006 \001(\tB\003\340A\003\022>\n\025earliest" + + "_version_time\030\007 \001(\0132\032.google.protobuf.Ti" + + "mestampB\003\340A\003\"M\n\005State\022\025\n\021STATE_UNSPECIFI" + + "ED\020\000\022\014\n\010CREATING\020\001\022\t\n\005READY\020\002\022\024\n\020READY_O" + + "PTIMIZING\020\003:b\352A_\n\037spanner.googleapis.com" + + "/Database\022\202\323\344\223\002/\022-/v1/{parent=proj" + + "ects/*/instances/*}/databases\332A\006parent\022\244" + + "\002\n\016CreateDatabase\0227.google.spanner.admin" + + ".database.v1.CreateDatabaseRequest\032\035.goo" + + "gle.longrunning.Operation\"\271\001\202\323\344\223\0022\"-/v1/" + + "{parent=projects/*/instances/*}/database" + + "s:\001*\332A\027parent,create_statement\312Ad\n)googl" + + "e.spanner.admin.database.v1.Database\0227go" + + "ogle.spanner.admin.database.v1.CreateDat" + + "abaseMetadata\022\255\001\n\013GetDatabase\0224.google.s" + + "panner.admin.database.v1.GetDatabaseRequ" + + "est\032*.google.spanner.admin.database.v1.D" + + "atabase\"<\202\323\344\223\002/\022-/v1/{name=projects/*/in" + + "stances/*/databases/*}\332A\004name\022\235\002\n\021Update" + + "DatabaseDdl\022:.google.spanner.admin.datab" + + "ase.v1.UpdateDatabaseDdlRequest\032\035.google" + + ".longrunning.Operation\"\254\001\202\323\344\223\002:25/v1/{da" + + "tabase=projects/*/instances/*/databases/" + + "*}/ddl:\001*\332A\023database,statements\312AS\n\025goog" + + "le.protobuf.Empty\022:google.spanner.admin." + + "database.v1.UpdateDatabaseDdlMetadata\022\243\001" + + "\n\014DropDatabase\0225.google.spanner.admin.da" + + "tabase.v1.DropDatabaseRequest\032\026.google.p" + + "rotobuf.Empty\"D\202\323\344\223\0023*1/v1/{database=pro" + + "jects/*/instances/*/databases/*}\332A\010datab" + + "ase\022\315\001\n\016GetDatabaseDdl\0227.google.spanner." + + "admin.database.v1.GetDatabaseDdlRequest\032" + + "8.google.spanner.admin.database.v1.GetDa" + + "tabaseDdlResponse\"H\202\323\344\223\0027\0225/v1/{database" + + "=projects/*/instances/*/databases/*}/ddl" + + "\332A\010database\022\353\001\n\014SetIamPolicy\022\".google.ia" + + "m.v1.SetIamPolicyRequest\032\025.google.iam.v1" + + ".Policy\"\237\001\202\323\344\223\002\206\001\">/v1/{resource=project" + + "s/*/instances/*/databases/*}:setIamPolic" + + "y:\001*ZA\"/v1/{resource=projects/" + + "*/instances/*/databases/*}:getIamPolicy:" + + "\001*ZA\"" + + ".google.spanner.admin.database.v1.ListBa" + + "ckupOperationsResponse\"E\202\323\344\223\0026\0224/v1/{par" + + "ent=projects/*/instances/*}/backupOperat" + + "ions\332A\006parent\032x\312A\026spanner.googleapis.com" + + "\322A\\https://www.googleapis.com/auth/cloud" + + "-platform,https://www.googleapis.com/aut" + + "h/spanner.adminB\332\002\n$com.google.spanner.a" + + "dmin.database.v1B\031SpannerDatabaseAdminPr" + + "otoP\001ZHgoogle.golang.org/genproto/google" + + "apis/spanner/admin/database/v1;database\252" + + "\002&Google.Cloud.Spanner.Admin.Database.V1" + + "\312\002&Google\\Cloud\\Spanner\\Admin\\Database\\V" + + "1\352\002+Google::Cloud::Spanner::Admin::Datab" + + "ase::V1\352AJ\n\037spanner.googleapis.com/Insta" + + "nce\022\'projects/{project}/instances/{insta" + + "nce}b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( @@ -321,7 +326,12 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_google_spanner_admin_database_v1_Database_descriptor, new java.lang.String[] { - "Name", "State", "CreateTime", "RestoreInfo", + "Name", + "State", + "CreateTime", + "RestoreInfo", + "VersionRetentionPeriod", + "EarliestVersionTime", }); internal_static_google_spanner_admin_database_v1_ListDatabasesRequest_descriptor = getDescriptor().getMessageTypes().get(2); diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/backup.proto b/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/backup.proto index e33faddddf4..a677207f729 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/backup.proto +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/backup.proto @@ -61,6 +61,12 @@ message Backup { type: "spanner.googleapis.com/Database" }]; + // The backup will contain an externally consistent copy of the database at + // the timestamp specified by `version_time`. If `version_time` is not + // specified, the system will set `version_time` to the `create_time` of the + // backup. + google.protobuf.Timestamp version_time = 9; + // Required for the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] // operation. The expiration time of the backup, with microseconds // granularity that must be at least 6 hours and at most 366 days @@ -84,10 +90,9 @@ message Backup { // `projects//instances/`. string name = 1; - // Output only. The backup will contain an externally consistent - // copy of the database at the timestamp specified by - // `create_time`. `create_time` is approximately the time the - // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request is received. + // Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // request is received. If the request does not specify `version_time`, the + // `version_time` of the backup will be equivalent to the `create_time`. google.protobuf.Timestamp create_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. Size of the backup in bytes. @@ -134,10 +139,14 @@ message CreateBackupRequest { // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]. message CreateBackupMetadata { // The name of the backup being created. - string name = 1; + string name = 1 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Backup" + }]; // The name of the database the backup is created from. - string database = 2; + string database = 2 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Database" + }]; // The progress of the // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] operation. @@ -311,9 +320,9 @@ message ListBackupOperationsRequest { // * `done:true` - The operation is complete. // * `metadata.database:prod` - The database the backup was taken from has // a name containing the string "prod". - // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND`
    - // `(metadata.name:howl) AND`
    - // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND`
    + // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ + // `(metadata.name:howl) AND` \ + // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ // `(error:*)` - Returns operations where: // * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. // * The backup name contains the string "howl". @@ -355,12 +364,23 @@ message ListBackupOperationsResponse { // Information about a backup. message BackupInfo { // Name of the backup. - string backup = 1; + string backup = 1 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Backup" + }]; // The backup contains an externally consistent copy of `source_database` at - // the timestamp specified by `create_time`. + // the timestamp specified by `version_time`. If the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify + // `version_time`, the `version_time` of the backup is equivalent to the + // `create_time`. + google.protobuf.Timestamp version_time = 4; + + // The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was + // received. google.protobuf.Timestamp create_time = 2; // Name of the database the backup was created from. - string source_database = 3; + string source_database = 3 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Database" + }]; } diff --git a/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/spanner_database_admin.proto b/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/spanner_database_admin.proto index db6192bc026..12e751bd672 100644 --- a/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/spanner_database_admin.proto +++ b/proto-google-cloud-spanner-admin-database-v1/src/main/proto/google/spanner/admin/database/v1/spanner_database_admin.proto @@ -368,6 +368,17 @@ message Database { // Output only. Applicable only for restored databases. Contains information // about the restore source. RestoreInfo restore_info = 4 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. The period in which Cloud Spanner retains all versions of data + // for the database. This is the same as the value of version_retention_period + // database option set using + // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour, + // if not set. + string version_retention_period = 6 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Earliest timestamp at which older versions of the data can be + // read. + google.protobuf.Timestamp earliest_version_time = 7 [(google.api.field_behavior) = OUTPUT_ONLY]; } // The request for [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases]. @@ -535,6 +546,8 @@ message DropDatabaseRequest { // The request for [GetDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDdl]. message GetDatabaseDdlRequest { // Required. The database whose schema we wish to get. + // Values are of the form + // `projects//instances//databases/` string database = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = { @@ -590,11 +603,11 @@ message ListDatabaseOperationsRequest { // Here are a few examples: // // * `done:true` - The operation is complete. - // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND`
    - // `(metadata.source_type:BACKUP) AND`
    - // `(metadata.backup_info.backup:backup_howl) AND`
    - // `(metadata.name:restored_howl) AND`
    - // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND`
    + // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ + // `(metadata.source_type:BACKUP) AND` \ + // `(metadata.backup_info.backup:backup_howl) AND` \ + // `(metadata.name:restored_howl) AND` \ + // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ // `(error:*)` - Return operations where: // * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. // * The database is restored from a backup. @@ -666,7 +679,9 @@ message RestoreDatabaseRequest { // [RestoreDatabase][google.spanner.admin.database.v1.DatabaseAdmin.RestoreDatabase]. message RestoreDatabaseMetadata { // Name of the database being created and restored to. - string name = 1; + string name = 1 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Database" + }]; // The type of the restore source. RestoreSourceType source_type = 2; @@ -716,7 +731,9 @@ message RestoreDatabaseMetadata { // completion of a database restore, and cannot be cancelled. message OptimizeRestoredDatabaseMetadata { // Name of the restored database being optimized. - string name = 1; + string name = 1 [(google.api.resource_reference) = { + type: "spanner.googleapis.com/Database" + }]; // The progress of the post-restore optimizations. OperationProgress progress = 2; diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 26298e14ca7..5de65e2e2a6 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 - 3.3.2 + 4.0.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 - 3.3.2 + 4.0.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index e2c914656e7..d1251b3ff1b 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 - 3.3.2 + 4.0.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 3.3.2 + 4.0.0 diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequest.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequest.java index f322ec04c09..1ba7b235bbb 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequest.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequest.java @@ -111,6 +111,11 @@ private CommitRequest( input.readMessage(com.google.spanner.v1.Mutation.parser(), extensionRegistry)); break; } + case 40: + { + returnCommitStats_ = input.readBool(); + break; + } default: { if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { @@ -422,6 +427,26 @@ public com.google.spanner.v1.MutationOrBuilder getMutationsOrBuilder(int index) return mutations_.get(index); } + public static final int RETURN_COMMIT_STATS_FIELD_NUMBER = 5; + private boolean returnCommitStats_; + /** + * + * + *
    +   * If `true`, then statistics related to the transaction will be included in
    +   * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +   * `false`.
    +   * 
    + * + * bool return_commit_stats = 5; + * + * @return The returnCommitStats. + */ + @java.lang.Override + public boolean getReturnCommitStats() { + return returnCommitStats_; + } + private byte memoizedIsInitialized = -1; @java.lang.Override @@ -448,6 +473,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io for (int i = 0; i < mutations_.size(); i++) { output.writeMessage(4, mutations_.get(i)); } + if (returnCommitStats_ != false) { + output.writeBool(5, returnCommitStats_); + } unknownFields.writeTo(output); } @@ -473,6 +501,9 @@ public int getSerializedSize() { for (int i = 0; i < mutations_.size(); i++) { size += com.google.protobuf.CodedOutputStream.computeMessageSize(4, mutations_.get(i)); } + if (returnCommitStats_ != false) { + size += com.google.protobuf.CodedOutputStream.computeBoolSize(5, returnCommitStats_); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -490,6 +521,7 @@ public boolean equals(final java.lang.Object obj) { if (!getSession().equals(other.getSession())) return false; if (!getMutationsList().equals(other.getMutationsList())) return false; + if (getReturnCommitStats() != other.getReturnCommitStats()) return false; if (!getTransactionCase().equals(other.getTransactionCase())) return false; switch (transactionCase_) { case 2: @@ -518,6 +550,8 @@ public int hashCode() { hash = (37 * hash) + MUTATIONS_FIELD_NUMBER; hash = (53 * hash) + getMutationsList().hashCode(); } + hash = (37 * hash) + RETURN_COMMIT_STATS_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(getReturnCommitStats()); switch (transactionCase_) { case 2: hash = (37 * hash) + TRANSACTION_ID_FIELD_NUMBER; @@ -684,6 +718,8 @@ public Builder clear() { } else { mutationsBuilder_.clear(); } + returnCommitStats_ = false; + transactionCase_ = 0; transaction_ = null; return this; @@ -733,6 +769,7 @@ public com.google.spanner.v1.CommitRequest buildPartial() { } else { result.mutations_ = mutationsBuilder_.build(); } + result.returnCommitStats_ = returnCommitStats_; result.transactionCase_ = transactionCase_; onBuilt(); return result; @@ -814,6 +851,9 @@ public Builder mergeFrom(com.google.spanner.v1.CommitRequest other) { } } } + if (other.getReturnCommitStats() != false) { + setReturnCommitStats(other.getReturnCommitStats()); + } switch (other.getTransactionCase()) { case TRANSACTION_ID: { @@ -1712,6 +1752,64 @@ public java.util.List getMutationsBuilde return mutationsBuilder_; } + private boolean returnCommitStats_; + /** + * + * + *
    +     * If `true`, then statistics related to the transaction will be included in
    +     * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +     * `false`.
    +     * 
    + * + * bool return_commit_stats = 5; + * + * @return The returnCommitStats. + */ + @java.lang.Override + public boolean getReturnCommitStats() { + return returnCommitStats_; + } + /** + * + * + *
    +     * If `true`, then statistics related to the transaction will be included in
    +     * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +     * `false`.
    +     * 
    + * + * bool return_commit_stats = 5; + * + * @param value The returnCommitStats to set. + * @return This builder for chaining. + */ + public Builder setReturnCommitStats(boolean value) { + + returnCommitStats_ = value; + onChanged(); + return this; + } + /** + * + * + *
    +     * If `true`, then statistics related to the transaction will be included in
    +     * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +     * `false`.
    +     * 
    + * + * bool return_commit_stats = 5; + * + * @return This builder for chaining. + */ + public Builder clearReturnCommitStats() { + + returnCommitStats_ = false; + onChanged(); + return this; + } + @java.lang.Override public final Builder setUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequestOrBuilder.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequestOrBuilder.java index bb0f6c6acdd..a32fa4b4396 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequestOrBuilder.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitRequestOrBuilder.java @@ -185,5 +185,20 @@ public interface CommitRequestOrBuilder */ com.google.spanner.v1.MutationOrBuilder getMutationsOrBuilder(int index); + /** + * + * + *
    +   * If `true`, then statistics related to the transaction will be included in
    +   * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +   * `false`.
    +   * 
    + * + * bool return_commit_stats = 5; + * + * @return The returnCommitStats. + */ + boolean getReturnCommitStats(); + public com.google.spanner.v1.CommitRequest.TransactionCase getTransactionCase(); } diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponse.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponse.java index 28772858452..320d165cf94 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponse.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponse.java @@ -81,6 +81,22 @@ private CommitResponse( commitTimestamp_ = subBuilder.buildPartial(); } + break; + } + case 18: + { + com.google.spanner.v1.CommitResponse.CommitStats.Builder subBuilder = null; + if (commitStats_ != null) { + subBuilder = commitStats_.toBuilder(); + } + commitStats_ = + input.readMessage( + com.google.spanner.v1.CommitResponse.CommitStats.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(commitStats_); + commitStats_ = subBuilder.buildPartial(); + } + break; } default: @@ -117,6 +133,586 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { com.google.spanner.v1.CommitResponse.Builder.class); } + public interface CommitStatsOrBuilder + extends + // @@protoc_insertion_point(interface_extends:google.spanner.v1.CommitResponse.CommitStats) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
    +     * The total number of mutations for the transaction. Knowing the
    +     * `mutation_count` value can help you maximize the number of mutations
    +     * in a transaction and minimize the number of API round trips. You can
    +     * also monitor this value to prevent transactions from exceeding the system
    +     * [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +     * If the number of mutations exceeds the limit, the server returns
    +     * [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +     * 
    + * + * int64 mutation_count = 1; + * + * @return The mutationCount. + */ + long getMutationCount(); + } + /** + * + * + *
    +   * Additional statistics about a commit.
    +   * 
    + * + * Protobuf type {@code google.spanner.v1.CommitResponse.CommitStats} + */ + public static final class CommitStats extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:google.spanner.v1.CommitResponse.CommitStats) + CommitStatsOrBuilder { + private static final long serialVersionUID = 0L; + // Use CommitStats.newBuilder() to construct. + private CommitStats(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private CommitStats() {} + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance(UnusedPrivateParameter unused) { + return new CommitStats(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet getUnknownFields() { + return this.unknownFields; + } + + private CommitStats( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + mutationCount_ = input.readInt64(); + break; + } + default: + { + if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return com.google.spanner.v1.SpannerProto + .internal_static_google_spanner_v1_CommitResponse_CommitStats_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.google.spanner.v1.SpannerProto + .internal_static_google_spanner_v1_CommitResponse_CommitStats_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.google.spanner.v1.CommitResponse.CommitStats.class, + com.google.spanner.v1.CommitResponse.CommitStats.Builder.class); + } + + public static final int MUTATION_COUNT_FIELD_NUMBER = 1; + private long mutationCount_; + /** + * + * + *
    +     * The total number of mutations for the transaction. Knowing the
    +     * `mutation_count` value can help you maximize the number of mutations
    +     * in a transaction and minimize the number of API round trips. You can
    +     * also monitor this value to prevent transactions from exceeding the system
    +     * [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +     * If the number of mutations exceeds the limit, the server returns
    +     * [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +     * 
    + * + * int64 mutation_count = 1; + * + * @return The mutationCount. + */ + @java.lang.Override + public long getMutationCount() { + return mutationCount_; + } + + private byte memoizedIsInitialized = -1; + + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { + if (mutationCount_ != 0L) { + output.writeInt64(1, mutationCount_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (mutationCount_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, mutationCount_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.google.spanner.v1.CommitResponse.CommitStats)) { + return super.equals(obj); + } + com.google.spanner.v1.CommitResponse.CommitStats other = + (com.google.spanner.v1.CommitResponse.CommitStats) obj; + + if (getMutationCount() != other.getMutationCount()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + MUTATION_COUNT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getMutationCount()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseDelimitedFrom( + java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseDelimitedFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + com.google.protobuf.CodedInputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(com.google.spanner.v1.CommitResponse.CommitStats prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * + * + *
    +     * Additional statistics about a commit.
    +     * 
    + * + * Protobuf type {@code google.spanner.v1.CommitResponse.CommitStats} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:google.spanner.v1.CommitResponse.CommitStats) + com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return com.google.spanner.v1.SpannerProto + .internal_static_google_spanner_v1_CommitResponse_CommitStats_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.google.spanner.v1.SpannerProto + .internal_static_google_spanner_v1_CommitResponse_CommitStats_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.google.spanner.v1.CommitResponse.CommitStats.class, + com.google.spanner.v1.CommitResponse.CommitStats.Builder.class); + } + + // Construct using com.google.spanner.v1.CommitResponse.CommitStats.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) {} + } + + @java.lang.Override + public Builder clear() { + super.clear(); + mutationCount_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return com.google.spanner.v1.SpannerProto + .internal_static_google_spanner_v1_CommitResponse_CommitStats_descriptor; + } + + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStats getDefaultInstanceForType() { + return com.google.spanner.v1.CommitResponse.CommitStats.getDefaultInstance(); + } + + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStats build() { + com.google.spanner.v1.CommitResponse.CommitStats result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStats buildPartial() { + com.google.spanner.v1.CommitResponse.CommitStats result = + new com.google.spanner.v1.CommitResponse.CommitStats(this); + result.mutationCount_ = mutationCount_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.setField(field, value); + } + + @java.lang.Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @java.lang.Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.addRepeatedField(field, value); + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.google.spanner.v1.CommitResponse.CommitStats) { + return mergeFrom((com.google.spanner.v1.CommitResponse.CommitStats) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.google.spanner.v1.CommitResponse.CommitStats other) { + if (other == com.google.spanner.v1.CommitResponse.CommitStats.getDefaultInstance()) + return this; + if (other.getMutationCount() != 0L) { + setMutationCount(other.getMutationCount()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.spanner.v1.CommitResponse.CommitStats parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = + (com.google.spanner.v1.CommitResponse.CommitStats) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private long mutationCount_; + /** + * + * + *
    +       * The total number of mutations for the transaction. Knowing the
    +       * `mutation_count` value can help you maximize the number of mutations
    +       * in a transaction and minimize the number of API round trips. You can
    +       * also monitor this value to prevent transactions from exceeding the system
    +       * [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +       * If the number of mutations exceeds the limit, the server returns
    +       * [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +       * 
    + * + * int64 mutation_count = 1; + * + * @return The mutationCount. + */ + @java.lang.Override + public long getMutationCount() { + return mutationCount_; + } + /** + * + * + *
    +       * The total number of mutations for the transaction. Knowing the
    +       * `mutation_count` value can help you maximize the number of mutations
    +       * in a transaction and minimize the number of API round trips. You can
    +       * also monitor this value to prevent transactions from exceeding the system
    +       * [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +       * If the number of mutations exceeds the limit, the server returns
    +       * [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +       * 
    + * + * int64 mutation_count = 1; + * + * @param value The mutationCount to set. + * @return This builder for chaining. + */ + public Builder setMutationCount(long value) { + + mutationCount_ = value; + onChanged(); + return this; + } + /** + * + * + *
    +       * The total number of mutations for the transaction. Knowing the
    +       * `mutation_count` value can help you maximize the number of mutations
    +       * in a transaction and minimize the number of API round trips. You can
    +       * also monitor this value to prevent transactions from exceeding the system
    +       * [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +       * If the number of mutations exceeds the limit, the server returns
    +       * [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +       * 
    + * + * int64 mutation_count = 1; + * + * @return This builder for chaining. + */ + public Builder clearMutationCount() { + + mutationCount_ = 0L; + onChanged(); + return this; + } + + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:google.spanner.v1.CommitResponse.CommitStats) + } + + // @@protoc_insertion_point(class_scope:google.spanner.v1.CommitResponse.CommitStats) + private static final com.google.spanner.v1.CommitResponse.CommitStats DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new com.google.spanner.v1.CommitResponse.CommitStats(); + } + + public static com.google.spanner.v1.CommitResponse.CommitStats getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CommitStats parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new CommitStats(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStats getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + public static final int COMMIT_TIMESTAMP_FIELD_NUMBER = 1; private com.google.protobuf.Timestamp commitTimestamp_; /** @@ -165,6 +761,60 @@ public com.google.protobuf.TimestampOrBuilder getCommitTimestampOrBuilder() { return getCommitTimestamp(); } + public static final int COMMIT_STATS_FIELD_NUMBER = 2; + private com.google.spanner.v1.CommitResponse.CommitStats commitStats_; + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return Whether the commitStats field is set. + */ + @java.lang.Override + public boolean hasCommitStats() { + return commitStats_ != null; + } + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return The commitStats. + */ + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStats getCommitStats() { + return commitStats_ == null + ? com.google.spanner.v1.CommitResponse.CommitStats.getDefaultInstance() + : commitStats_; + } + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + @java.lang.Override + public com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder getCommitStatsOrBuilder() { + return getCommitStats(); + } + private byte memoizedIsInitialized = -1; @java.lang.Override @@ -182,6 +832,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io if (commitTimestamp_ != null) { output.writeMessage(1, getCommitTimestamp()); } + if (commitStats_ != null) { + output.writeMessage(2, getCommitStats()); + } unknownFields.writeTo(output); } @@ -194,6 +847,9 @@ public int getSerializedSize() { if (commitTimestamp_ != null) { size += com.google.protobuf.CodedOutputStream.computeMessageSize(1, getCommitTimestamp()); } + if (commitStats_ != null) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(2, getCommitStats()); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -213,6 +869,10 @@ public boolean equals(final java.lang.Object obj) { if (hasCommitTimestamp()) { if (!getCommitTimestamp().equals(other.getCommitTimestamp())) return false; } + if (hasCommitStats() != other.hasCommitStats()) return false; + if (hasCommitStats()) { + if (!getCommitStats().equals(other.getCommitStats())) return false; + } if (!unknownFields.equals(other.unknownFields)) return false; return true; } @@ -228,6 +888,10 @@ public int hashCode() { hash = (37 * hash) + COMMIT_TIMESTAMP_FIELD_NUMBER; hash = (53 * hash) + getCommitTimestamp().hashCode(); } + if (hasCommitStats()) { + hash = (37 * hash) + COMMIT_STATS_FIELD_NUMBER; + hash = (53 * hash) + getCommitStats().hashCode(); + } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -378,6 +1042,12 @@ public Builder clear() { commitTimestamp_ = null; commitTimestampBuilder_ = null; } + if (commitStatsBuilder_ == null) { + commitStats_ = null; + } else { + commitStats_ = null; + commitStatsBuilder_ = null; + } return this; } @@ -409,6 +1079,11 @@ public com.google.spanner.v1.CommitResponse buildPartial() { } else { result.commitTimestamp_ = commitTimestampBuilder_.build(); } + if (commitStatsBuilder_ == null) { + result.commitStats_ = commitStats_; + } else { + result.commitStats_ = commitStatsBuilder_.build(); + } onBuilt(); return result; } @@ -461,6 +1136,9 @@ public Builder mergeFrom(com.google.spanner.v1.CommitResponse other) { if (other.hasCommitTimestamp()) { mergeCommitTimestamp(other.getCommitTimestamp()); } + if (other.hasCommitStats()) { + mergeCommitStats(other.getCommitStats()); + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -675,6 +1353,210 @@ public com.google.protobuf.TimestampOrBuilder getCommitTimestampOrBuilder() { return commitTimestampBuilder_; } + private com.google.spanner.v1.CommitResponse.CommitStats commitStats_; + private com.google.protobuf.SingleFieldBuilderV3< + com.google.spanner.v1.CommitResponse.CommitStats, + com.google.spanner.v1.CommitResponse.CommitStats.Builder, + com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder> + commitStatsBuilder_; + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return Whether the commitStats field is set. + */ + public boolean hasCommitStats() { + return commitStatsBuilder_ != null || commitStats_ != null; + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return The commitStats. + */ + public com.google.spanner.v1.CommitResponse.CommitStats getCommitStats() { + if (commitStatsBuilder_ == null) { + return commitStats_ == null + ? com.google.spanner.v1.CommitResponse.CommitStats.getDefaultInstance() + : commitStats_; + } else { + return commitStatsBuilder_.getMessage(); + } + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public Builder setCommitStats(com.google.spanner.v1.CommitResponse.CommitStats value) { + if (commitStatsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + commitStats_ = value; + onChanged(); + } else { + commitStatsBuilder_.setMessage(value); + } + + return this; + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public Builder setCommitStats( + com.google.spanner.v1.CommitResponse.CommitStats.Builder builderForValue) { + if (commitStatsBuilder_ == null) { + commitStats_ = builderForValue.build(); + onChanged(); + } else { + commitStatsBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public Builder mergeCommitStats(com.google.spanner.v1.CommitResponse.CommitStats value) { + if (commitStatsBuilder_ == null) { + if (commitStats_ != null) { + commitStats_ = + com.google.spanner.v1.CommitResponse.CommitStats.newBuilder(commitStats_) + .mergeFrom(value) + .buildPartial(); + } else { + commitStats_ = value; + } + onChanged(); + } else { + commitStatsBuilder_.mergeFrom(value); + } + + return this; + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public Builder clearCommitStats() { + if (commitStatsBuilder_ == null) { + commitStats_ = null; + onChanged(); + } else { + commitStats_ = null; + commitStatsBuilder_ = null; + } + + return this; + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public com.google.spanner.v1.CommitResponse.CommitStats.Builder getCommitStatsBuilder() { + + onChanged(); + return getCommitStatsFieldBuilder().getBuilder(); + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + public com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder getCommitStatsOrBuilder() { + if (commitStatsBuilder_ != null) { + return commitStatsBuilder_.getMessageOrBuilder(); + } else { + return commitStats_ == null + ? com.google.spanner.v1.CommitResponse.CommitStats.getDefaultInstance() + : commitStats_; + } + } + /** + * + * + *
    +     * The statistics about this Commit. Not returned by default.
    +     * For more information, see
    +     * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +     * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.google.spanner.v1.CommitResponse.CommitStats, + com.google.spanner.v1.CommitResponse.CommitStats.Builder, + com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder> + getCommitStatsFieldBuilder() { + if (commitStatsBuilder_ == null) { + commitStatsBuilder_ = + new com.google.protobuf.SingleFieldBuilderV3< + com.google.spanner.v1.CommitResponse.CommitStats, + com.google.spanner.v1.CommitResponse.CommitStats.Builder, + com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder>( + getCommitStats(), getParentForChildren(), isClean()); + commitStats_ = null; + } + return commitStatsBuilder_; + } + @java.lang.Override public final Builder setUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponseOrBuilder.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponseOrBuilder.java index 90c6c6e6729..9ff076ff849 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponseOrBuilder.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/CommitResponseOrBuilder.java @@ -57,4 +57,45 @@ public interface CommitResponseOrBuilder * .google.protobuf.Timestamp commit_timestamp = 1; */ com.google.protobuf.TimestampOrBuilder getCommitTimestampOrBuilder(); + + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return Whether the commitStats field is set. + */ + boolean hasCommitStats(); + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + * + * @return The commitStats. + */ + com.google.spanner.v1.CommitResponse.CommitStats getCommitStats(); + /** + * + * + *
    +   * The statistics about this Commit. Not returned by default.
    +   * For more information, see
    +   * [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +   * 
    + * + * .google.spanner.v1.CommitResponse.CommitStats commit_stats = 2; + */ + com.google.spanner.v1.CommitResponse.CommitStatsOrBuilder getCommitStatsOrBuilder(); } diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequest.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequest.java index 1e757805fd5..79a7ad822b4 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequest.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequest.java @@ -402,6 +402,9 @@ public interface QueryOptionsOrBuilder * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -426,6 +429,9 @@ public interface QueryOptionsOrBuilder * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -546,6 +552,9 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -581,6 +590,9 @@ public java.lang.String getOptimizerVersion() { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -941,6 +953,9 @@ public Builder mergeFrom( * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -975,6 +990,9 @@ public java.lang.String getOptimizerVersion() { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -1009,6 +1027,9 @@ public com.google.protobuf.ByteString getOptimizerVersionBytes() { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -1042,6 +1063,9 @@ public Builder setOptimizerVersion(java.lang.String value) { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -1071,6 +1095,9 @@ public Builder clearOptimizerVersion() { * SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement * with an invalid optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. + * See + * https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer + * for more information on managing the query optimizer. * The `optimizer_version` statement hint has precedence over this setting. * * @@ -1318,8 +1345,9 @@ public com.google.protobuf.ByteString getSqlBytes() { *
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -1340,8 +1368,9 @@ public boolean hasParams() {
        * 
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -1362,8 +1391,9 @@ public com.google.protobuf.Struct getParams() {
        * 
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2656,8 +2686,9 @@ public Builder setSqlBytes(com.google.protobuf.ByteString value) {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2677,8 +2708,9 @@ public boolean hasParams() {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2702,8 +2734,9 @@ public com.google.protobuf.Struct getParams() {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2731,8 +2764,9 @@ public Builder setParams(com.google.protobuf.Struct value) {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2757,8 +2791,9 @@ public Builder setParams(com.google.protobuf.Struct.Builder builderForValue) {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2787,8 +2822,9 @@ public Builder mergeParams(com.google.protobuf.Struct value) {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2814,8 +2850,9 @@ public Builder clearParams() {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2835,8 +2872,9 @@ public com.google.protobuf.Struct.Builder getParamsBuilder() {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -2858,8 +2896,9 @@ public com.google.protobuf.StructOrBuilder getParamsOrBuilder() {
          * 
          * Parameter names and values that bind to placeholders in the SQL string.
          * A parameter placeholder consists of the `@` character followed by the
    -     * parameter name (for example, `@firstName`). Parameter names can contain
    -     * letters, numbers, and underscores.
    +     * parameter name (for example, `@firstName`). Parameter names must conform
    +     * to the naming requirements of identifiers as specified at
    +     * https://cloud.google.com/spanner/docs/lexical#identifiers.
          * Parameters can appear anywhere that a literal value is expected.  The same
          * parameter name can be used more than once, for example:
          * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequestOrBuilder.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequestOrBuilder.java
    index 7971beeb49c..d6af9b0ba6c 100644
    --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequestOrBuilder.java
    +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/ExecuteSqlRequestOrBuilder.java
    @@ -136,8 +136,9 @@ public interface ExecuteSqlRequestOrBuilder
        * 
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -155,8 +156,9 @@ public interface ExecuteSqlRequestOrBuilder
        * 
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    @@ -174,8 +176,9 @@ public interface ExecuteSqlRequestOrBuilder
        * 
        * Parameter names and values that bind to placeholders in the SQL string.
        * A parameter placeholder consists of the `@` character followed by the
    -   * parameter name (for example, `@firstName`). Parameter names can contain
    -   * letters, numbers, and underscores.
    +   * parameter name (for example, `@firstName`). Parameter names must conform
    +   * to the naming requirements of identifiers as specified at
    +   * https://cloud.google.com/spanner/docs/lexical#identifiers.
        * Parameters can appear anywhere that a literal value is expected.  The same
        * parameter name can be used more than once, for example:
        * `"WHERE id > @msg_id AND id < @msg_id + 100"`
    diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Session.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Session.java
    index 9fe4f10c7cb..1b058eb621f 100644
    --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Session.java
    +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Session.java
    @@ -170,11 +170,10 @@ protected com.google.protobuf.MapField internalGetMapField(int number) {
        *
        *
        * 
    -   * The name of the session. This is always system-assigned; values provided
    -   * when creating a session are ignored.
    +   * Output only. The name of the session. This is always system-assigned.
        * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The name. */ @@ -194,11 +193,10 @@ public java.lang.String getName() { * * *
    -   * The name of the session. This is always system-assigned; values provided
    -   * when creating a session are ignored.
    +   * Output only. The name of the session. This is always system-assigned.
        * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The bytes for name. */ @@ -346,7 +344,8 @@ public java.lang.String getLabelsOrThrow(java.lang.String key) { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the createTime field is set. */ @@ -361,7 +360,8 @@ public boolean hasCreateTime() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The createTime. */ @@ -376,7 +376,8 @@ public com.google.protobuf.Timestamp getCreateTime() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ @java.lang.Override public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { @@ -393,7 +394,9 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * typically earlier than the actual last use time. *
    * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the approximateLastUseTime field is set. */ @@ -409,7 +412,9 @@ public boolean hasApproximateLastUseTime() { * typically earlier than the actual last use time. *
    * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The approximateLastUseTime. */ @@ -427,7 +432,9 @@ public com.google.protobuf.Timestamp getApproximateLastUseTime() { * typically earlier than the actual last use time. *
    * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ @java.lang.Override public com.google.protobuf.TimestampOrBuilder getApproximateLastUseTimeOrBuilder() { @@ -852,11 +859,10 @@ public Builder mergeFrom( * * *
    -     * The name of the session. This is always system-assigned; values provided
    -     * when creating a session are ignored.
    +     * Output only. The name of the session. This is always system-assigned.
          * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The name. */ @@ -875,11 +881,10 @@ public java.lang.String getName() { * * *
    -     * The name of the session. This is always system-assigned; values provided
    -     * when creating a session are ignored.
    +     * Output only. The name of the session. This is always system-assigned.
          * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The bytes for name. */ @@ -898,11 +903,10 @@ public com.google.protobuf.ByteString getNameBytes() { * * *
    -     * The name of the session. This is always system-assigned; values provided
    -     * when creating a session are ignored.
    +     * Output only. The name of the session. This is always system-assigned.
          * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @param value The name to set. * @return This builder for chaining. @@ -920,11 +924,10 @@ public Builder setName(java.lang.String value) { * * *
    -     * The name of the session. This is always system-assigned; values provided
    -     * when creating a session are ignored.
    +     * Output only. The name of the session. This is always system-assigned.
          * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return This builder for chaining. */ @@ -938,11 +941,10 @@ public Builder clearName() { * * *
    -     * The name of the session. This is always system-assigned; values provided
    -     * when creating a session are ignored.
    +     * Output only. The name of the session. This is always system-assigned.
          * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @param value The bytes for name to set. * @return This builder for chaining. @@ -1170,7 +1172,9 @@ public Builder putAllLabels(java.util.Map va * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the createTime field is set. */ @@ -1184,7 +1188,9 @@ public boolean hasCreateTime() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The createTime. */ @@ -1204,7 +1210,9 @@ public com.google.protobuf.Timestamp getCreateTime() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder setCreateTime(com.google.protobuf.Timestamp value) { if (createTimeBuilder_ == null) { @@ -1226,7 +1234,9 @@ public Builder setCreateTime(com.google.protobuf.Timestamp value) { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder setCreateTime(com.google.protobuf.Timestamp.Builder builderForValue) { if (createTimeBuilder_ == null) { @@ -1245,7 +1255,9 @@ public Builder setCreateTime(com.google.protobuf.Timestamp.Builder builderForVal * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder mergeCreateTime(com.google.protobuf.Timestamp value) { if (createTimeBuilder_ == null) { @@ -1269,7 +1281,9 @@ public Builder mergeCreateTime(com.google.protobuf.Timestamp value) { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder clearCreateTime() { if (createTimeBuilder_ == null) { @@ -1289,7 +1303,9 @@ public Builder clearCreateTime() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public com.google.protobuf.Timestamp.Builder getCreateTimeBuilder() { @@ -1303,7 +1319,9 @@ public com.google.protobuf.Timestamp.Builder getCreateTimeBuilder() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { if (createTimeBuilder_ != null) { @@ -1321,7 +1339,9 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * Output only. The timestamp when the session is created. *
    * - * .google.protobuf.Timestamp create_time = 3; + * + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ private com.google.protobuf.SingleFieldBuilderV3< com.google.protobuf.Timestamp, @@ -1354,7 +1374,9 @@ public com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder() { * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the approximateLastUseTime field is set. */ @@ -1369,7 +1391,9 @@ public boolean hasApproximateLastUseTime() { * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The approximateLastUseTime. */ @@ -1390,7 +1414,9 @@ public com.google.protobuf.Timestamp getApproximateLastUseTime() { * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder setApproximateLastUseTime(com.google.protobuf.Timestamp value) { if (approximateLastUseTimeBuilder_ == null) { @@ -1413,7 +1439,9 @@ public Builder setApproximateLastUseTime(com.google.protobuf.Timestamp value) { * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder setApproximateLastUseTime( com.google.protobuf.Timestamp.Builder builderForValue) { @@ -1434,7 +1462,9 @@ public Builder setApproximateLastUseTime( * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder mergeApproximateLastUseTime(com.google.protobuf.Timestamp value) { if (approximateLastUseTimeBuilder_ == null) { @@ -1461,7 +1491,9 @@ public Builder mergeApproximateLastUseTime(com.google.protobuf.Timestamp value) * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public Builder clearApproximateLastUseTime() { if (approximateLastUseTimeBuilder_ == null) { @@ -1482,7 +1514,9 @@ public Builder clearApproximateLastUseTime() { * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public com.google.protobuf.Timestamp.Builder getApproximateLastUseTimeBuilder() { @@ -1497,7 +1531,9 @@ public com.google.protobuf.Timestamp.Builder getApproximateLastUseTimeBuilder() * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ public com.google.protobuf.TimestampOrBuilder getApproximateLastUseTimeOrBuilder() { if (approximateLastUseTimeBuilder_ != null) { @@ -1516,7 +1552,9 @@ public com.google.protobuf.TimestampOrBuilder getApproximateLastUseTimeOrBuilder * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ private com.google.protobuf.SingleFieldBuilderV3< com.google.protobuf.Timestamp, diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SessionOrBuilder.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SessionOrBuilder.java index 6b7fc5b822a..556832d0093 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SessionOrBuilder.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SessionOrBuilder.java @@ -27,11 +27,10 @@ public interface SessionOrBuilder * * *
    -   * The name of the session. This is always system-assigned; values provided
    -   * when creating a session are ignored.
    +   * Output only. The name of the session. This is always system-assigned.
        * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The name. */ @@ -40,11 +39,10 @@ public interface SessionOrBuilder * * *
    -   * The name of the session. This is always system-assigned; values provided
    -   * when creating a session are ignored.
    +   * Output only. The name of the session. This is always system-assigned.
        * 
    * - * string name = 1; + * string name = 1 [(.google.api.field_behavior) = OUTPUT_ONLY]; * * @return The bytes for name. */ @@ -141,7 +139,8 @@ public interface SessionOrBuilder * Output only. The timestamp when the session is created. * * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the createTime field is set. */ @@ -153,7 +152,8 @@ public interface SessionOrBuilder * Output only. The timestamp when the session is created. * * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The createTime. */ @@ -165,7 +165,8 @@ public interface SessionOrBuilder * Output only. The timestamp when the session is created. * * - * .google.protobuf.Timestamp create_time = 3; + * .google.protobuf.Timestamp create_time = 3 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ com.google.protobuf.TimestampOrBuilder getCreateTimeOrBuilder(); @@ -177,7 +178,9 @@ public interface SessionOrBuilder * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return Whether the approximateLastUseTime field is set. */ @@ -190,7 +193,9 @@ public interface SessionOrBuilder * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * * * @return The approximateLastUseTime. */ @@ -203,7 +208,9 @@ public interface SessionOrBuilder * typically earlier than the actual last use time. * * - * .google.protobuf.Timestamp approximate_last_use_time = 4; + * + * .google.protobuf.Timestamp approximate_last_use_time = 4 [(.google.api.field_behavior) = OUTPUT_ONLY]; + * */ com.google.protobuf.TimestampOrBuilder getApproximateLastUseTimeOrBuilder(); } diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerProto.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerProto.java index 0932ab5d52a..8458063adac 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerProto.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/SpannerProto.java @@ -131,6 +131,10 @@ public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry r internal_static_google_spanner_v1_CommitResponse_descriptor; static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_google_spanner_v1_CommitResponse_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_google_spanner_v1_CommitResponse_CommitStats_descriptor; + static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_google_spanner_v1_CommitResponse_CommitStats_fieldAccessorTable; static final com.google.protobuf.Descriptors.Descriptor internal_static_google_spanner_v1_RollbackRequest_descriptor; static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable @@ -148,198 +152,202 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + "e.spanner.v1\032\034google/api/annotations.pro" + "to\032\027google/api/client.proto\032\037google/api/" + "field_behavior.proto\032\031google/api/resourc" - + "e.proto\032\033google/protobuf/empty.proto\032\034go" - + "ogle/protobuf/struct.proto\032\037google/proto" - + "buf/timestamp.proto\032\027google/rpc/status.p" - + "roto\032\034google/spanner/v1/keys.proto\032 goog" - + "le/spanner/v1/mutation.proto\032\"google/spa" - + "nner/v1/result_set.proto\032#google/spanner" - + "/v1/transaction.proto\032\034google/spanner/v1" - + "/type.proto\"~\n\024CreateSessionRequest\0229\n\010d" - + "atabase\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapi" - + "s.com/Database\022+\n\007session\030\002 \001(\0132\032.google" - + ".spanner.v1.Session\"\251\001\n\032BatchCreateSessi" - + "onsRequest\0229\n\010database\030\001 \001(\tB\'\340A\002\372A!\n\037sp" - + "anner.googleapis.com/Database\0224\n\020session" - + "_template\030\002 \001(\0132\032.google.spanner.v1.Sess" - + "ion\022\032\n\rsession_count\030\003 \001(\005B\003\340A\002\"J\n\033Batch" - + "CreateSessionsResponse\022+\n\007session\030\001 \003(\0132" - + "\032.google.spanner.v1.Session\"\344\002\n\007Session\022" - + "\014\n\004name\030\001 \001(\t\0226\n\006labels\030\002 \003(\0132&.google.s" - + "panner.v1.Session.LabelsEntry\022/\n\013create_" - + "time\030\003 \001(\0132\032.google.protobuf.Timestamp\022=" - + "\n\031approximate_last_use_time\030\004 \001(\0132\032.goog" - + "le.protobuf.Timestamp\032-\n\013LabelsEntry\022\013\n\003" - + "key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001:t\352Aq\n\036spann" - + "er.googleapis.com/Session\022Oprojects/{pro" - + "ject}/instances/{instance}/databases/{da" - + "tabase}/sessions/{session}\"I\n\021GetSession" - + "Request\0224\n\004name\030\001 \001(\tB&\340A\002\372A \n\036spanner.g" - + "oogleapis.com/Session\"\207\001\n\023ListSessionsRe" - + "quest\0229\n\010database\030\001 \001(\tB\'\340A\002\372A!\n\037spanner" - + ".googleapis.com/Database\022\021\n\tpage_size\030\002 " - + "\001(\005\022\022\n\npage_token\030\003 \001(\t\022\016\n\006filter\030\004 \001(\t\"" - + "]\n\024ListSessionsResponse\022,\n\010sessions\030\001 \003(" - + "\0132\032.google.spanner.v1.Session\022\027\n\017next_pa" - + "ge_token\030\002 \001(\t\"L\n\024DeleteSessionRequest\0224" - + "\n\004name\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis" - + ".com/Session\"\202\005\n\021ExecuteSqlRequest\0227\n\007se" - + "ssion\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis." - + "com/Session\022;\n\013transaction\030\002 \001(\0132&.googl" - + "e.spanner.v1.TransactionSelector\022\020\n\003sql\030" - + "\003 \001(\tB\003\340A\002\022\'\n\006params\030\004 \001(\0132\027.google.prot" - + "obuf.Struct\022I\n\013param_types\030\005 \003(\01324.googl" - + "e.spanner.v1.ExecuteSqlRequest.ParamType" - + "sEntry\022\024\n\014resume_token\030\006 \001(\014\022B\n\nquery_mo" - + "de\030\007 \001(\0162..google.spanner.v1.ExecuteSqlR" - + "equest.QueryMode\022\027\n\017partition_token\030\010 \001(" - + "\014\022\r\n\005seqno\030\t \001(\003\022H\n\rquery_options\030\n \001(\0132" - + "1.google.spanner.v1.ExecuteSqlRequest.Qu" - + "eryOptions\032)\n\014QueryOptions\022\031\n\021optimizer_" - + "version\030\001 \001(\t\032J\n\017ParamTypesEntry\022\013\n\003key\030" - + "\001 \001(\t\022&\n\005value\030\002 \001(\0132\027.google.spanner.v1" - + ".Type:\0028\001\".\n\tQueryMode\022\n\n\006NORMAL\020\000\022\010\n\004PL" - + "AN\020\001\022\013\n\007PROFILE\020\002\"\337\003\n\026ExecuteBatchDmlReq" - + "uest\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036spanner.g" - + "oogleapis.com/Session\022@\n\013transaction\030\002 \001" - + "(\0132&.google.spanner.v1.TransactionSelect" - + "orB\003\340A\002\022L\n\nstatements\030\003 \003(\01323.google.spa" - + "nner.v1.ExecuteBatchDmlRequest.Statement" - + "B\003\340A\002\022\022\n\005seqno\030\004 \001(\003B\003\340A\002\032\347\001\n\tStatement\022" - + "\013\n\003sql\030\001 \001(\t\022\'\n\006params\030\002 \001(\0132\027.google.pr" - + "otobuf.Struct\022X\n\013param_types\030\003 \003(\0132C.goo" + + "e.proto\032\036google/protobuf/duration.proto\032" + + "\033google/protobuf/empty.proto\032\034google/pro" + + "tobuf/struct.proto\032\037google/protobuf/time" + + "stamp.proto\032\027google/rpc/status.proto\032\034go" + + "ogle/spanner/v1/keys.proto\032 google/spann" + + "er/v1/mutation.proto\032\"google/spanner/v1/" + + "result_set.proto\032#google/spanner/v1/tran" + + "saction.proto\032\034google/spanner/v1/type.pr" + + "oto\"~\n\024CreateSessionRequest\0229\n\010database\030" + + "\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapis.com/Da" + + "tabase\022+\n\007session\030\002 \001(\0132\032.google.spanner" + + ".v1.Session\"\251\001\n\032BatchCreateSessionsReque" + + "st\0229\n\010database\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.go" + + "ogleapis.com/Database\0224\n\020session_templat" + + "e\030\002 \001(\0132\032.google.spanner.v1.Session\022\032\n\rs" + + "ession_count\030\003 \001(\005B\003\340A\002\"J\n\033BatchCreateSe" + + "ssionsResponse\022+\n\007session\030\001 \003(\0132\032.google" + + ".spanner.v1.Session\"\363\002\n\007Session\022\021\n\004name\030" + + "\001 \001(\tB\003\340A\003\0226\n\006labels\030\002 \003(\0132&.google.span" + + "ner.v1.Session.LabelsEntry\0224\n\013create_tim" + + "e\030\003 \001(\0132\032.google.protobuf.TimestampB\003\340A\003" + + "\022B\n\031approximate_last_use_time\030\004 \001(\0132\032.go" + + "ogle.protobuf.TimestampB\003\340A\003\032-\n\013LabelsEn" + + "try\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001:t\352Aq" + + "\n\036spanner.googleapis.com/Session\022Oprojec" + + "ts/{project}/instances/{instance}/databa" + + "ses/{database}/sessions/{session}\"I\n\021Get" + + "SessionRequest\0224\n\004name\030\001 \001(\tB&\340A\002\372A \n\036sp" + + "anner.googleapis.com/Session\"\207\001\n\023ListSes" + + "sionsRequest\0229\n\010database\030\001 \001(\tB\'\340A\002\372A!\n\037" + + "spanner.googleapis.com/Database\022\021\n\tpage_" + + "size\030\002 \001(\005\022\022\n\npage_token\030\003 \001(\t\022\016\n\006filter" + + "\030\004 \001(\t\"]\n\024ListSessionsResponse\022,\n\010sessio" + + "ns\030\001 \003(\0132\032.google.spanner.v1.Session\022\027\n\017" + + "next_page_token\030\002 \001(\t\"L\n\024DeleteSessionRe" + + "quest\0224\n\004name\030\001 \001(\tB&\340A\002\372A \n\036spanner.goo" + + "gleapis.com/Session\"\202\005\n\021ExecuteSqlReques" + + "t\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036spanner.goog" + + "leapis.com/Session\022;\n\013transaction\030\002 \001(\0132" + + "&.google.spanner.v1.TransactionSelector\022" + + "\020\n\003sql\030\003 \001(\tB\003\340A\002\022\'\n\006params\030\004 \001(\0132\027.goog" + + "le.protobuf.Struct\022I\n\013param_types\030\005 \003(\0132" + + "4.google.spanner.v1.ExecuteSqlRequest.Pa" + + "ramTypesEntry\022\024\n\014resume_token\030\006 \001(\014\022B\n\nq" + + "uery_mode\030\007 \001(\0162..google.spanner.v1.Exec" + + "uteSqlRequest.QueryMode\022\027\n\017partition_tok" + + "en\030\010 \001(\014\022\r\n\005seqno\030\t \001(\003\022H\n\rquery_options" + + "\030\n \001(\01321.google.spanner.v1.ExecuteSqlReq" + + "uest.QueryOptions\032)\n\014QueryOptions\022\031\n\021opt" + + "imizer_version\030\001 \001(\t\032J\n\017ParamTypesEntry\022" + + "\013\n\003key\030\001 \001(\t\022&\n\005value\030\002 \001(\0132\027.google.spa" + + "nner.v1.Type:\0028\001\".\n\tQueryMode\022\n\n\006NORMAL\020" + + "\000\022\010\n\004PLAN\020\001\022\013\n\007PROFILE\020\002\"\337\003\n\026ExecuteBatc" + + "hDmlRequest\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036sp" + + "anner.googleapis.com/Session\022@\n\013transact" + + "ion\030\002 \001(\0132&.google.spanner.v1.Transactio" + + "nSelectorB\003\340A\002\022L\n\nstatements\030\003 \003(\01323.goo" + "gle.spanner.v1.ExecuteBatchDmlRequest.St" - + "atement.ParamTypesEntry\032J\n\017ParamTypesEnt" - + "ry\022\013\n\003key\030\001 \001(\t\022&\n\005value\030\002 \001(\0132\027.google." - + "spanner.v1.Type:\0028\001\"p\n\027ExecuteBatchDmlRe" - + "sponse\0221\n\013result_sets\030\001 \003(\0132\034.google.spa" - + "nner.v1.ResultSet\022\"\n\006status\030\002 \001(\0132\022.goog" - + "le.rpc.Status\"H\n\020PartitionOptions\022\034\n\024par" - + "tition_size_bytes\030\001 \001(\003\022\026\n\016max_partition" - + "s\030\002 \001(\003\"\243\003\n\025PartitionQueryRequest\0227\n\007ses" - + "sion\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis.c" - + "om/Session\022;\n\013transaction\030\002 \001(\0132&.google" - + ".spanner.v1.TransactionSelector\022\020\n\003sql\030\003" - + " \001(\tB\003\340A\002\022\'\n\006params\030\004 \001(\0132\027.google.proto" - + "buf.Struct\022M\n\013param_types\030\005 \003(\01328.google" - + ".spanner.v1.PartitionQueryRequest.ParamT" - + "ypesEntry\022>\n\021partition_options\030\006 \001(\0132#.g" - + "oogle.spanner.v1.PartitionOptions\032J\n\017Par" - + "amTypesEntry\022\013\n\003key\030\001 \001(\t\022&\n\005value\030\002 \001(\013" - + "2\027.google.spanner.v1.Type:\0028\001\"\261\002\n\024Partit" - + "ionReadRequest\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n" - + "\036spanner.googleapis.com/Session\022;\n\013trans" - + "action\030\002 \001(\0132&.google.spanner.v1.Transac" - + "tionSelector\022\022\n\005table\030\003 \001(\tB\003\340A\002\022\r\n\005inde" - + "x\030\004 \001(\t\022\017\n\007columns\030\005 \003(\t\022/\n\007key_set\030\006 \001(" - + "\0132\031.google.spanner.v1.KeySetB\003\340A\002\022>\n\021par" - + "tition_options\030\t \001(\0132#.google.spanner.v1" - + ".PartitionOptions\"$\n\tPartition\022\027\n\017partit" - + "ion_token\030\001 \001(\014\"z\n\021PartitionResponse\0220\n\n" - + "partitions\030\001 \003(\0132\034.google.spanner.v1.Par" - + "tition\0223\n\013transaction\030\002 \001(\0132\036.google.spa" - + "nner.v1.Transaction\"\253\002\n\013ReadRequest\0227\n\007s" - + "ession\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis" - + ".com/Session\022;\n\013transaction\030\002 \001(\0132&.goog" - + "le.spanner.v1.TransactionSelector\022\022\n\005tab" - + "le\030\003 \001(\tB\003\340A\002\022\r\n\005index\030\004 \001(\t\022\024\n\007columns\030" - + "\005 \003(\tB\003\340A\002\022/\n\007key_set\030\006 \001(\0132\031.google.spa" - + "nner.v1.KeySetB\003\340A\002\022\r\n\005limit\030\010 \001(\003\022\024\n\014re" - + "sume_token\030\t \001(\014\022\027\n\017partition_token\030\n \001(" - + "\014\"\217\001\n\027BeginTransactionRequest\0227\n\007session" - + "\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis.com/S" - + "ession\022;\n\007options\030\002 \001(\0132%.google.spanner" - + ".v1.TransactionOptionsB\003\340A\002\"\352\001\n\rCommitRe" - + "quest\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036spanner." - + "googleapis.com/Session\022\030\n\016transaction_id" - + "\030\002 \001(\014H\000\022G\n\026single_use_transaction\030\003 \001(\013" - + "2%.google.spanner.v1.TransactionOptionsH" - + "\000\022.\n\tmutations\030\004 \003(\0132\033.google.spanner.v1" - + ".MutationB\r\n\013transaction\"F\n\016CommitRespon" - + "se\0224\n\020commit_timestamp\030\001 \001(\0132\032.google.pr" - + "otobuf.Timestamp\"g\n\017RollbackRequest\0227\n\007s" - + "ession\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapis" - + ".com/Session\022\033\n\016transaction_id\030\002 \001(\014B\003\340A" - + "\0022\300\026\n\007Spanner\022\246\001\n\rCreateSession\022\'.google" - + ".spanner.v1.CreateSessionRequest\032\032.googl" - + "e.spanner.v1.Session\"P\202\323\344\223\002?\":/v1/{datab" - + "ase=projects/*/instances/*/databases/*}/" - + "sessions:\001*\332A\010database\022\340\001\n\023BatchCreateSe" - + "ssions\022-.google.spanner.v1.BatchCreateSe" - + "ssionsRequest\032..google.spanner.v1.BatchC" - + "reateSessionsResponse\"j\202\323\344\223\002K\"F/v1/{data" - + "base=projects/*/instances/*/databases/*}" - + "/sessions:batchCreate:\001*\332A\026database,sess" - + "ion_count\022\227\001\n\nGetSession\022$.google.spanne" - + "r.v1.GetSessionRequest\032\032.google.spanner." - + "v1.Session\"G\202\323\344\223\002:\0228/v1/{name=projects/*" - + "/instances/*/databases/*/sessions/*}\332A\004n" - + "ame\022\256\001\n\014ListSessions\022&.google.spanner.v1" - + ".ListSessionsRequest\032\'.google.spanner.v1" - + ".ListSessionsResponse\"M\202\323\344\223\002<\022:/v1/{data" - + "base=projects/*/instances/*/databases/*}" - + "/sessions\332A\010database\022\231\001\n\rDeleteSession\022\'" - + ".google.spanner.v1.DeleteSessionRequest\032" - + "\026.google.protobuf.Empty\"G\202\323\344\223\002:*8/v1/{na" - + "me=projects/*/instances/*/databases/*/se" - + "ssions/*}\332A\004name\022\243\001\n\nExecuteSql\022$.google" - + ".spanner.v1.ExecuteSqlRequest\032\034.google.s" - + "panner.v1.ResultSet\"Q\202\323\344\223\002K\"F/v1/{sessio" - + "n=projects/*/instances/*/databases/*/ses" - + "sions/*}:executeSql:\001*\022\276\001\n\023ExecuteStream" - + "ingSql\022$.google.spanner.v1.ExecuteSqlReq" - + "uest\032#.google.spanner.v1.PartialResultSe" - + "t\"Z\202\323\344\223\002T\"O/v1/{session=projects/*/insta" - + "nces/*/databases/*/sessions/*}:executeSt" - + "reamingSql:\001*0\001\022\300\001\n\017ExecuteBatchDml\022).go" - + "ogle.spanner.v1.ExecuteBatchDmlRequest\032*" - + ".google.spanner.v1.ExecuteBatchDmlRespon" - + "se\"V\202\323\344\223\002P\"K/v1/{session=projects/*/inst" - + "ances/*/databases/*/sessions/*}:executeB" - + "atchDml:\001*\022\221\001\n\004Read\022\036.google.spanner.v1." - + "ReadRequest\032\034.google.spanner.v1.ResultSe" - + "t\"K\202\323\344\223\002E\"@/v1/{session=projects/*/insta" - + "nces/*/databases/*/sessions/*}:read:\001*\022\254" - + "\001\n\rStreamingRead\022\036.google.spanner.v1.Rea" - + "dRequest\032#.google.spanner.v1.PartialResu" - + "ltSet\"T\202\323\344\223\002N\"I/v1/{session=projects/*/i" - + "nstances/*/databases/*/sessions/*}:strea" - + "mingRead:\001*0\001\022\311\001\n\020BeginTransaction\022*.goo" - + "gle.spanner.v1.BeginTransactionRequest\032\036" - + ".google.spanner.v1.Transaction\"i\202\323\344\223\002Q\"L" + + "atementB\003\340A\002\022\022\n\005seqno\030\004 \001(\003B\003\340A\002\032\347\001\n\tSta" + + "tement\022\013\n\003sql\030\001 \001(\t\022\'\n\006params\030\002 \001(\0132\027.go" + + "ogle.protobuf.Struct\022X\n\013param_types\030\003 \003(" + + "\0132C.google.spanner.v1.ExecuteBatchDmlReq" + + "uest.Statement.ParamTypesEntry\032J\n\017ParamT" + + "ypesEntry\022\013\n\003key\030\001 \001(\t\022&\n\005value\030\002 \001(\0132\027." + + "google.spanner.v1.Type:\0028\001\"p\n\027ExecuteBat" + + "chDmlResponse\0221\n\013result_sets\030\001 \003(\0132\034.goo" + + "gle.spanner.v1.ResultSet\022\"\n\006status\030\002 \001(\013" + + "2\022.google.rpc.Status\"H\n\020PartitionOptions" + + "\022\034\n\024partition_size_bytes\030\001 \001(\003\022\026\n\016max_pa" + + "rtitions\030\002 \001(\003\"\243\003\n\025PartitionQueryRequest" + + "\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036spanner.googl" + + "eapis.com/Session\022;\n\013transaction\030\002 \001(\0132&" + + ".google.spanner.v1.TransactionSelector\022\020" + + "\n\003sql\030\003 \001(\tB\003\340A\002\022\'\n\006params\030\004 \001(\0132\027.googl" + + "e.protobuf.Struct\022M\n\013param_types\030\005 \003(\01328" + + ".google.spanner.v1.PartitionQueryRequest" + + ".ParamTypesEntry\022>\n\021partition_options\030\006 " + + "\001(\0132#.google.spanner.v1.PartitionOptions" + + "\032J\n\017ParamTypesEntry\022\013\n\003key\030\001 \001(\t\022&\n\005valu" + + "e\030\002 \001(\0132\027.google.spanner.v1.Type:\0028\001\"\261\002\n" + + "\024PartitionReadRequest\0227\n\007session\030\001 \001(\tB&" + + "\340A\002\372A \n\036spanner.googleapis.com/Session\022;" + + "\n\013transaction\030\002 \001(\0132&.google.spanner.v1." + + "TransactionSelector\022\022\n\005table\030\003 \001(\tB\003\340A\002\022" + + "\r\n\005index\030\004 \001(\t\022\017\n\007columns\030\005 \003(\t\022/\n\007key_s" + + "et\030\006 \001(\0132\031.google.spanner.v1.KeySetB\003\340A\002" + + "\022>\n\021partition_options\030\t \001(\0132#.google.spa" + + "nner.v1.PartitionOptions\"$\n\tPartition\022\027\n" + + "\017partition_token\030\001 \001(\014\"z\n\021PartitionRespo" + + "nse\0220\n\npartitions\030\001 \003(\0132\034.google.spanner" + + ".v1.Partition\0223\n\013transaction\030\002 \001(\0132\036.goo" + + "gle.spanner.v1.Transaction\"\253\002\n\013ReadReque" + + "st\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036spanner.goo" + + "gleapis.com/Session\022;\n\013transaction\030\002 \001(\013" + + "2&.google.spanner.v1.TransactionSelector" + + "\022\022\n\005table\030\003 \001(\tB\003\340A\002\022\r\n\005index\030\004 \001(\t\022\024\n\007c" + + "olumns\030\005 \003(\tB\003\340A\002\022/\n\007key_set\030\006 \001(\0132\031.goo" + + "gle.spanner.v1.KeySetB\003\340A\002\022\r\n\005limit\030\010 \001(" + + "\003\022\024\n\014resume_token\030\t \001(\014\022\027\n\017partition_tok" + + "en\030\n \001(\014\"\217\001\n\027BeginTransactionRequest\0227\n\007" + + "session\030\001 \001(\tB&\340A\002\372A \n\036spanner.googleapi" + + "s.com/Session\022;\n\007options\030\002 \001(\0132%.google." + + "spanner.v1.TransactionOptionsB\003\340A\002\"\207\002\n\rC" + + "ommitRequest\0227\n\007session\030\001 \001(\tB&\340A\002\372A \n\036s" + + "panner.googleapis.com/Session\022\030\n\016transac" + + "tion_id\030\002 \001(\014H\000\022G\n\026single_use_transactio" + + "n\030\003 \001(\0132%.google.spanner.v1.TransactionO" + + "ptionsH\000\022.\n\tmutations\030\004 \003(\0132\033.google.spa" + + "nner.v1.Mutation\022\033\n\023return_commit_stats\030" + + "\005 \001(\010B\r\n\013transaction\"\262\001\n\016CommitResponse\022" + + "4\n\020commit_timestamp\030\001 \001(\0132\032.google.proto" + + "buf.Timestamp\022C\n\014commit_stats\030\002 \001(\0132-.go" + + "ogle.spanner.v1.CommitResponse.CommitSta" + + "ts\032%\n\013CommitStats\022\026\n\016mutation_count\030\001 \001(" + + "\003\"g\n\017RollbackRequest\0227\n\007session\030\001 \001(\tB&\340" + + "A\002\372A \n\036spanner.googleapis.com/Session\022\033\n" + + "\016transaction_id\030\002 \001(\014B\003\340A\0022\300\026\n\007Spanner\022\246" + + "\001\n\rCreateSession\022\'.google.spanner.v1.Cre" + + "ateSessionRequest\032\032.google.spanner.v1.Se" + + "ssion\"P\202\323\344\223\002?\":/v1/{database=projects/*/" + + "instances/*/databases/*}/sessions:\001*\332A\010d" + + "atabase\022\340\001\n\023BatchCreateSessions\022-.google" + + ".spanner.v1.BatchCreateSessionsRequest\032." + + ".google.spanner.v1.BatchCreateSessionsRe" + + "sponse\"j\202\323\344\223\002K\"F/v1/{database=projects/*" + + "/instances/*/databases/*}/sessions:batch" + + "Create:\001*\332A\026database,session_count\022\227\001\n\nG" + + "etSession\022$.google.spanner.v1.GetSession" + + "Request\032\032.google.spanner.v1.Session\"G\202\323\344" + + "\223\002:\0228/v1/{name=projects/*/instances/*/da" + + "tabases/*/sessions/*}\332A\004name\022\256\001\n\014ListSes" + + "sions\022&.google.spanner.v1.ListSessionsRe" + + "quest\032\'.google.spanner.v1.ListSessionsRe" + + "sponse\"M\202\323\344\223\002<\022:/v1/{database=projects/*" + + "/instances/*/databases/*}/sessions\332A\010dat" + + "abase\022\231\001\n\rDeleteSession\022\'.google.spanner" + + ".v1.DeleteSessionRequest\032\026.google.protob" + + "uf.Empty\"G\202\323\344\223\002:*8/v1/{name=projects/*/i" + + "nstances/*/databases/*/sessions/*}\332A\004nam" + + "e\022\243\001\n\nExecuteSql\022$.google.spanner.v1.Exe" + + "cuteSqlRequest\032\034.google.spanner.v1.Resul" + + "tSet\"Q\202\323\344\223\002K\"F/v1/{session=projects/*/in" + + "stances/*/databases/*/sessions/*}:execut" + + "eSql:\001*\022\276\001\n\023ExecuteStreamingSql\022$.google" + + ".spanner.v1.ExecuteSqlRequest\032#.google.s" + + "panner.v1.PartialResultSet\"Z\202\323\344\223\002T\"O/v1/" + + "{session=projects/*/instances/*/database" + + "s/*/sessions/*}:executeStreamingSql:\001*0\001" + + "\022\300\001\n\017ExecuteBatchDml\022).google.spanner.v1" + + ".ExecuteBatchDmlRequest\032*.google.spanner" + + ".v1.ExecuteBatchDmlResponse\"V\202\323\344\223\002P\"K/v1" + + "/{session=projects/*/instances/*/databas" + + "es/*/sessions/*}:executeBatchDml:\001*\022\221\001\n\004" + + "Read\022\036.google.spanner.v1.ReadRequest\032\034.g" + + "oogle.spanner.v1.ResultSet\"K\202\323\344\223\002E\"@/v1/" + + "{session=projects/*/instances/*/database" + + "s/*/sessions/*}:read:\001*\022\254\001\n\rStreamingRea" + + "d\022\036.google.spanner.v1.ReadRequest\032#.goog" + + "le.spanner.v1.PartialResultSet\"T\202\323\344\223\002N\"I" + "/v1/{session=projects/*/instances/*/data" - + "bases/*/sessions/*}:beginTransaction:\001*\332" - + "A\017session,options\022\353\001\n\006Commit\022 .google.sp" - + "anner.v1.CommitRequest\032!.google.spanner." - + "v1.CommitResponse\"\233\001\202\323\344\223\002G\"B/v1/{session" - + "=projects/*/instances/*/databases/*/sess" - + "ions/*}:commit:\001*\332A session,transaction_" - + "id,mutations\332A(session,single_use_transa" - + "ction,mutations\022\260\001\n\010Rollback\022\".google.sp" - + "anner.v1.RollbackRequest\032\026.google.protob" - + "uf.Empty\"h\202\323\344\223\002I\"D/v1/{session=projects/" - + "*/instances/*/databases/*/sessions/*}:ro" - + "llback:\001*\332A\026session,transaction_id\022\267\001\n\016P" - + "artitionQuery\022(.google.spanner.v1.Partit" - + "ionQueryRequest\032$.google.spanner.v1.Part" - + "itionResponse\"U\202\323\344\223\002O\"J/v1/{session=proj" - + "ects/*/instances/*/databases/*/sessions/" - + "*}:partitionQuery:\001*\022\264\001\n\rPartitionRead\022\'" - + ".google.spanner.v1.PartitionReadRequest\032" - + "$.google.spanner.v1.PartitionResponse\"T\202" - + "\323\344\223\002N\"I/v1/{session=projects/*/instances" - + "/*/databases/*/sessions/*}:partitionRead" - + ":\001*\032w\312A\026spanner.googleapis.com\322A[https:/" - + "/www.googleapis.com/auth/cloud-platform," - + "https://www.googleapis.com/auth/spanner." - + "dataB\224\002\n\025com.google.spanner.v1B\014SpannerP" - + "rotoP\001Z8google.golang.org/genproto/googl" - + "eapis/spanner/v1;spanner\252\002\027Google.Cloud." - + "Spanner.V1\312\002\027Google\\Cloud\\Spanner\\V1\352\002\032G" - + "oogle::Cloud::Spanner::V1\352A_\n\037spanner.go" - + "ogleapis.com/Database\022 @@ -148,7 +148,7 @@ public interface FieldOrBuilder * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -311,7 +311,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -340,7 +340,7 @@ public java.lang.String getName() { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -764,7 +764,7 @@ public Builder mergeFrom( * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -792,7 +792,7 @@ public java.lang.String getName() { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -820,7 +820,7 @@ public com.google.protobuf.ByteString getNameBytes() { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -847,7 +847,7 @@ public Builder setName(java.lang.String value) { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * @@ -870,7 +870,7 @@ public Builder clearName() { * SQL queries, it is the column alias (e.g., `"Word"` in the * query `"SELECT 'hello' AS Word"`), or the column name (e.g., * `"ColName"` in the query `"SELECT ColName FROM Table"`). Some - * columns might have an empty name (e.g., !"SELECT + * columns might have an empty name (e.g., `"SELECT * UPPER(ColName)"`). Note that a query result can contain * multiple fields with the same name. * diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TransactionOptions.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TransactionOptions.java index 93de3f1b96a..980f749f00a 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TransactionOptions.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TransactionOptions.java @@ -23,10 +23,11 @@ * *
      * # Transactions
    - * Each session can have at most one active transaction at a time. After the
    - * active transaction is completed, the session can immediately be
    - * re-used for the next transaction. It is not necessary to create a
    - * new session for each transaction.
    + * Each session can have at most one active transaction at a time (note that
    + * standalone reads and queries use a transaction internally and do count
    + * towards the one transaction limit). After the active transaction is
    + * completed, the session can immediately be re-used for the next transaction.
    + * It is not necessary to create a new session for each transaction.
      * # Transaction Modes
      * Cloud Spanner supports three transaction modes:
      *   1. Locking read-write. This type of transaction is the only way
    @@ -4312,10 +4313,11 @@ protected Builder newBuilderForType(com.google.protobuf.GeneratedMessageV3.Build
        *
        * 
        * # Transactions
    -   * Each session can have at most one active transaction at a time. After the
    -   * active transaction is completed, the session can immediately be
    -   * re-used for the next transaction. It is not necessary to create a
    -   * new session for each transaction.
    +   * Each session can have at most one active transaction at a time (note that
    +   * standalone reads and queries use a transaction internally and do count
    +   * towards the one transaction limit). After the active transaction is
    +   * completed, the session can immediately be re-used for the next transaction.
    +   * It is not necessary to create a new session for each transaction.
        * # Transaction Modes
        * Cloud Spanner supports three transaction modes:
        *   1. Locking read-write. This type of transaction is the only way
    diff --git a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/spanner.proto b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/spanner.proto
    index 93e4987ed16..8f579e333d3 100644
    --- a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/spanner.proto
    +++ b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/spanner.proto
    @@ -20,6 +20,7 @@ import "google/api/annotations.proto";
     import "google/api/client.proto";
     import "google/api/field_behavior.proto";
     import "google/api/resource.proto";
    +import "google/protobuf/duration.proto";
     import "google/protobuf/empty.proto";
     import "google/protobuf/struct.proto";
     import "google/protobuf/timestamp.proto";
    @@ -219,6 +220,12 @@ service Spanner {
       // transactions. However, it can also happen for a variety of other
       // reasons. If `Commit` returns `ABORTED`, the caller should re-attempt
       // the transaction from the beginning, re-using the same session.
    +  //
    +  // On very rare occasions, `Commit` might return `UNKNOWN`. This can happen,
    +  // for example, if the client job experiences a 1+ hour networking failure.
    +  // At that point, Cloud Spanner has lost track of the transaction outcome and
    +  // we recommend that you perform another read from the database to see the
    +  // state of things as they are now.
       rpc Commit(CommitRequest) returns (CommitResponse) {
         option (google.api.http) = {
           post: "/v1/{session=projects/*/instances/*/databases/*/sessions/*}:commit"
    @@ -331,9 +338,8 @@ message Session {
         pattern: "projects/{project}/instances/{instance}/databases/{database}/sessions/{session}"
       };
     
    -  // The name of the session. This is always system-assigned; values provided
    -  // when creating a session are ignored.
    -  string name = 1;
    +  // Output only. The name of the session. This is always system-assigned.
    +  string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
     
       // The labels for the session.
       //
    @@ -347,11 +353,11 @@ message Session {
       map labels = 2;
     
       // Output only. The timestamp when the session is created.
    -  google.protobuf.Timestamp create_time = 3;
    +  google.protobuf.Timestamp create_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
     
       // Output only. The approximate timestamp when the session is last used. It is
       // typically earlier than the actual last use time.
    -  google.protobuf.Timestamp approximate_last_use_time = 4;
    +  google.protobuf.Timestamp approximate_last_use_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];
     }
     
     // The request for [GetSession][google.spanner.v1.Spanner.GetSession].
    @@ -438,6 +444,9 @@ message ExecuteSqlRequest {
         // SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS. Executing a SQL statement
         // with an invalid optimizer version will fail with a syntax error
         // (`INVALID_ARGUMENT`) status.
    +    // See
    +    // https://cloud.google.com/spanner/docs/query-optimizer/manage-query-optimizer
    +    // for more information on managing the query optimizer.
         //
         // The `optimizer_version` statement hint has precedence over this setting.
         string optimizer_version = 1;
    @@ -483,8 +492,9 @@ message ExecuteSqlRequest {
       // Parameter names and values that bind to placeholders in the SQL string.
       //
       // A parameter placeholder consists of the `@` character followed by the
    -  // parameter name (for example, `@firstName`). Parameter names can contain
    -  // letters, numbers, and underscores.
    +  // parameter name (for example, `@firstName`). Parameter names must conform
    +  // to the naming requirements of identifiers as specified at
    +  // https://cloud.google.com/spanner/docs/lexical#identifiers.
       //
       // Parameters can appear anywhere that a literal value is expected.  The same
       // parameter name can be used more than once, for example:
    @@ -884,12 +894,34 @@ message CommitRequest {
       // mutations are applied atomically, in the order they appear in
       // this list.
       repeated Mutation mutations = 4;
    +
    +  // If `true`, then statistics related to the transaction will be included in
    +  // the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. Default value is
    +  // `false`.
    +  bool return_commit_stats = 5;
     }
     
     // The response for [Commit][google.spanner.v1.Spanner.Commit].
     message CommitResponse {
    +  // Additional statistics about a commit.
    +  message CommitStats {
    +    // The total number of mutations for the transaction. Knowing the
    +    // `mutation_count` value can help you maximize the number of mutations
    +    // in a transaction and minimize the number of API round trips. You can
    +    // also monitor this value to prevent transactions from exceeding the system
    +    // [limit](http://cloud.google.com/spanner/quotas#limits_for_creating_reading_updating_and_deleting_data).
    +    // If the number of mutations exceeds the limit, the server returns
    +    // [INVALID_ARGUMENT](http://cloud.google.com/spanner/docs/reference/rest/v1/Code#ENUM_VALUES.INVALID_ARGUMENT).
    +    int64 mutation_count = 1;
    +  }
    +
       // The Cloud Spanner timestamp at which the transaction committed.
       google.protobuf.Timestamp commit_timestamp = 1;
    +
    +  // The statistics about this Commit. Not returned by default.
    +  // For more information, see
    +  // [CommitRequest.return_commit_stats][google.spanner.v1.CommitRequest.return_commit_stats].
    +  CommitStats commit_stats = 2;
     }
     
     // The request for [Rollback][google.spanner.v1.Spanner.Rollback].
    diff --git a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/transaction.proto b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/transaction.proto
    index 0bcbfcf9002..2cafefcb107 100644
    --- a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/transaction.proto
    +++ b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/transaction.proto
    @@ -31,10 +31,11 @@ option ruby_package = "Google::Cloud::Spanner::V1";
     // # Transactions
     //
     //
    -// Each session can have at most one active transaction at a time. After the
    -// active transaction is completed, the session can immediately be
    -// re-used for the next transaction. It is not necessary to create a
    -// new session for each transaction.
    +// Each session can have at most one active transaction at a time (note that
    +// standalone reads and queries use a transaction internally and do count
    +// towards the one transaction limit). After the active transaction is
    +// completed, the session can immediately be re-used for the next transaction.
    +// It is not necessary to create a new session for each transaction.
     //
     // # Transaction Modes
     //
    diff --git a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto
    index 1e5e5ff313a..1b863c0fdf4 100644
    --- a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto
    +++ b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto
    @@ -50,7 +50,7 @@ message StructType {
         // SQL queries, it is the column alias (e.g., `"Word"` in the
         // query `"SELECT 'hello' AS Word"`), or the column name (e.g.,
         // `"ColName"` in the query `"SELECT ColName FROM Table"`). Some
    -    // columns might have an empty name (e.g., !"SELECT
    +    // columns might have an empty name (e.g., `"SELECT
         // UPPER(ColName)"`). Note that a query result can contain
         // multiple fields with the same name.
         string name = 1;
    diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
    index 46b615b5cf2..b9c60d3097b 100644
    --- a/samples/install-without-bom/pom.xml
    +++ b/samples/install-without-bom/pom.xml
    @@ -22,8 +22,8 @@
         1.8
         UTF-8
         0.28.3
    -    1.2.10
    -    2.0.11
    +    1.2.11
    +    2.0.12
       
     
     
    @@ -32,7 +32,7 @@
         
           com.google.cloud
           google-cloud-spanner
    -      3.3.1
    +      3.3.2
         
         
         
    @@ -93,7 +93,7 @@
         
           junit
           junit
    -      4.13.1
    +      4.13.2
           test
         
         
    diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
    index 6dbbf8567bc..b3006bd7483 100644
    --- a/samples/snapshot/pom.xml
    +++ b/samples/snapshot/pom.xml
    @@ -22,8 +22,8 @@
         1.8
         UTF-8
         0.28.3
    -    1.2.10
    -    2.0.11
    +    1.2.11
    +    2.0.12
       
     
       
    @@ -31,7 +31,7 @@
         
           com.google.cloud
           google-cloud-spanner
    -      3.3.2
    +      4.0.0
         
         
         
    @@ -92,7 +92,7 @@
         
           junit
           junit
    -      4.13.1
    +      4.13.2
           test
         
         
    diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
    index 8fbf7b1b9e2..642eec6ea62 100644
    --- a/samples/snippets/pom.xml
    +++ b/samples/snippets/pom.xml
    @@ -33,7 +33,7 @@
           
             com.google.cloud
             libraries-bom
    -        16.3.0
    +        16.4.0
             pom
             import
           
    @@ -104,7 +104,7 @@
         
           junit
           junit
    -      4.13.1
    +      4.13.2
           test
         
         
    diff --git a/synth.metadata b/synth.metadata
    index 0a947958495..016c9d8aef1 100644
    --- a/synth.metadata
    +++ b/synth.metadata
    @@ -4,22 +4,22 @@
           "git": {
             "name": ".",
             "remote": "https://github.com/googleapis/java-spanner.git",
    -        "sha": "15052653993a3924037b2c4ba27e04f553493885"
    +        "sha": "326b8331d952005cf48eaa9efb054fab5918f7e9"
           }
         },
         {
           "git": {
             "name": "googleapis",
             "remote": "https://github.com/googleapis/googleapis.git",
    -        "sha": "91e206bcfeaf8948ea03fe3cb1b7616108496cd3",
    -        "internalRef": "350949863"
    +        "sha": "8d8c008e56f1af31d57f75561e0f1848ffb29eeb",
    +        "internalRef": "356341083"
           }
         },
         {
           "git": {
             "name": "synthtool",
             "remote": "https://github.com/googleapis/synthtool.git",
    -        "sha": "6133907dbb3ddab204a17a15d5c53ec0aae9b033"
    +        "sha": "f327d3b657a63ae4a8efd7f011a15eacae36b59c"
           }
         }
       ],
    @@ -60,7 +60,6 @@
         ".github/blunderbuss.yml",
         ".github/generated-files-bot.yml",
         ".github/readme/synth.py",
    -    ".github/release-please.yml",
         ".github/snippet-bot.yml",
         ".github/trusted-contribution.yml",
         ".github/workflows/approve-readme.yaml",
    @@ -75,7 +74,6 @@
         ".kokoro/continuous/java8.cfg",
         ".kokoro/continuous/readme.cfg",
         ".kokoro/dependencies.sh",
    -    ".kokoro/linkage-monitor.sh",
         ".kokoro/nightly/integration.cfg",
         ".kokoro/nightly/java11.cfg",
         ".kokoro/nightly/java7.cfg",
    @@ -103,6 +101,8 @@
         ".kokoro/release/promote.sh",
         ".kokoro/release/publish_javadoc.cfg",
         ".kokoro/release/publish_javadoc.sh",
    +    ".kokoro/release/publish_javadoc11.cfg",
    +    ".kokoro/release/publish_javadoc11.sh",
         ".kokoro/release/snapshot.cfg",
         ".kokoro/release/snapshot.sh",
         ".kokoro/release/stage.cfg",
    diff --git a/synth.py b/synth.py
    index 24d6b3074f4..a855da29c03 100644
    --- a/synth.py
    +++ b/synth.py
    @@ -89,4 +89,6 @@
         'samples/snapshot/pom.xml',
         'samples/snippets/pom.xml',
         '.github/CODEOWNERS',
    +    '.github/sync-repo-settings.yaml',
    +    '.github/release-please.yml',
     ])
    diff --git a/versions.txt b/versions.txt
    index 0664f8f8d34..d07c027940c 100644
    --- a/versions.txt
    +++ b/versions.txt
    @@ -1,10 +1,10 @@
     # Format:
     # module:released-version:current-version
     
    -proto-google-cloud-spanner-admin-instance-v1:3.3.2:3.3.2
    -proto-google-cloud-spanner-v1:3.3.2:3.3.2
    -proto-google-cloud-spanner-admin-database-v1:3.3.2:3.3.2
    -grpc-google-cloud-spanner-v1:3.3.2:3.3.2
    -grpc-google-cloud-spanner-admin-instance-v1:3.3.2:3.3.2
    -grpc-google-cloud-spanner-admin-database-v1:3.3.2:3.3.2
    -google-cloud-spanner:3.3.2:3.3.2
    \ No newline at end of file
    +proto-google-cloud-spanner-admin-instance-v1:4.0.0:4.0.0
    +proto-google-cloud-spanner-v1:4.0.0:4.0.0
    +proto-google-cloud-spanner-admin-database-v1:4.0.0:4.0.0
    +grpc-google-cloud-spanner-v1:4.0.0:4.0.0
    +grpc-google-cloud-spanner-admin-instance-v1:4.0.0:4.0.0
    +grpc-google-cloud-spanner-admin-database-v1:4.0.0:4.0.0
    +google-cloud-spanner:4.0.0:4.0.0
    \ No newline at end of file