diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml index 9399ebef235..4943e3e93ee 100644 --- a/.github/workflows/hermetic_library_generation.yaml +++ b/.github/workflows/hermetic_library_generation.yaml @@ -37,7 +37,7 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }} - - uses: googleapis/sdk-platform-java/.github/scripts@v2.47.0 + - uses: googleapis/sdk-platform-java/.github/scripts@v2.48.0 if: env.SHOULD_RUN == 'true' with: base_ref: ${{ github.base_ref }} diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index ce5a61f9f07..c167c82fed8 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -17,6 +17,6 @@ jobs: # repository .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.37.0 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.38.0 with: bom-path: google-cloud-spanner-bom/pom.xml diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg index 0f8b919c91f..9a49f7df62e 100644 --- a/.kokoro/presubmit/graalvm-native-17.cfg +++ b/.kokoro/presubmit/graalvm-native-17.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.37.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.38.0" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg index e6553bd6e41..e434ee68472 100644 --- a/.kokoro/presubmit/graalvm-native.cfg +++ b/.kokoro/presubmit/graalvm-native.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.37.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.38.0" } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index b31c0e6410f..c576408486e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [6.80.0](https://github.com/googleapis/java-spanner/compare/v6.79.0...v6.80.0) (2024-10-25) + + +### Features + +* Enabling endToEndTracing support in Connection API ([#3412](https://github.com/googleapis/java-spanner/issues/3412)) ([16cc6ee](https://github.com/googleapis/java-spanner/commit/16cc6eed58cf735026d7757a28f61f29821a14bf)) + + +### Dependencies + +* Update dependency com.google.cloud:sdk-platform-java-config to v3.38.0 ([#3424](https://github.com/googleapis/java-spanner/issues/3424)) ([b727453](https://github.com/googleapis/java-spanner/commit/b727453b93d1089f76e1b908255610cc2796da43)) +* Update dependency io.opentelemetry:opentelemetry-bom to v1.43.0 ([#3399](https://github.com/googleapis/java-spanner/issues/3399)) ([a755c6c](https://github.com/googleapis/java-spanner/commit/a755c6c2f44cc3eb0f5a54cd58244cebc62b7a4f)) +* Update dependency io.opentelemetry:opentelemetry-sdk-testing to v1.43.0 ([#3398](https://github.com/googleapis/java-spanner/issues/3398)) ([693243a](https://github.com/googleapis/java-spanner/commit/693243afae34610441345645f627bf199e8ddb8b)) +* Update googleapis/sdk-platform-java action to v2.48.0 ([#3422](https://github.com/googleapis/java-spanner/issues/3422)) ([d5d1f55](https://github.com/googleapis/java-spanner/commit/d5d1f55d7e8e8f9aa89b7ab9e5f5bd0464bf0e1a)) + + +### Documentation + +* Fix tracing sample to exit when completed, and use custom monitored resource for export ([#3287](https://github.com/googleapis/java-spanner/issues/3287)) ([ddb65b1](https://github.com/googleapis/java-spanner/commit/ddb65b197a6f311c2bb8ec9856ea968f3a31d62a)) + ## [6.79.0](https://github.com/googleapis/java-spanner/compare/v6.78.0...v6.79.0) (2024-10-11) diff --git a/README.md b/README.md index f3d24b256c6..35e60a85ce8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.48.0 + 26.49.0 pom import @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.76.0 + 6.79.0 ``` @@ -49,20 +49,20 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.48.0') +implementation platform('com.google.cloud:libraries-bom:26.49.0') implementation 'com.google.cloud:google-cloud-spanner' ``` If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-spanner:6.79.0' +implementation 'com.google.cloud:google-cloud-spanner:6.80.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.79.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.80.0" ``` ## Authentication @@ -719,7 +719,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.79.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.80.0 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index bc2c5880ea6..84345b34d6c 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -24,7 +24,7 @@ com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 @@ -34,7 +34,7 @@ UTF-8 UTF-8 2.10.1 - 1.42.1 + 1.43.0 @@ -85,14 +85,14 @@ io.opentelemetry opentelemetry-bom - 1.42.1 + 1.43.0 pom import com.google.cloud google-cloud-spanner - 6.78.0 + 6.79.0 commons-cli @@ -133,7 +133,7 @@ org.codehaus.mojo exec-maven-plugin - 3.4.1 + 3.5.0 com.google.cloud.spanner.benchmark.LatencyBenchmark false diff --git a/generation_config.yaml b/generation_config.yaml index 54b3a1cff92..ad36acc0af4 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,6 +1,6 @@ -gapic_generator_version: 2.47.0 -googleapis_commitish: de509e38d37a2a9d8b95e1ce78831189f4f3c0f4 -libraries_bom_version: 26.48.0 +gapic_generator_version: 2.48.0 +googleapis_commitish: 537fd482f6bb8afb3a146d9b21673a8eb27958bd +libraries_bom_version: 26.49.0 libraries: - api_shortname: spanner name_pretty: Cloud Spanner diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index bb8ab787c5c..b380542a7bf 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.79.0 + 6.80.0 pom com.google.cloud sdk-platform-java-config - 3.37.0 + 3.38.0 Google Cloud Spanner BOM @@ -53,43 +53,43 @@ com.google.cloud google-cloud-spanner - 6.79.0 + 6.80.0 com.google.cloud google-cloud-spanner test-jar - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index be176bf53e6..49f3cf946c2 100644 --- a/google-cloud-spanner-executor/pom.xml +++ b/google-cloud-spanner-executor/pom.xml @@ -5,14 +5,14 @@ 4.0.0 com.google.cloud google-cloud-spanner-executor - 6.79.0 + 6.80.0 jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/google-cloud-spanner-executor/src/main/resources/META-INF/native-image/com.google.cloud.spanner.executor.v1/reflect-config.json b/google-cloud-spanner-executor/src/main/resources/META-INF/native-image/com.google.cloud.spanner.executor.v1/reflect-config.json index 4fe624b7aa7..b3291f2748a 100644 --- a/google-cloud-spanner-executor/src/main/resources/META-INF/native-image/com.google.cloud.spanner.executor.v1/reflect-config.json +++ b/google-cloud-spanner-executor/src/main/resources/META-INF/native-image/com.google.cloud.spanner.executor.v1/reflect-config.json @@ -395,6 +395,24 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.api.SelectiveGapicGeneration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.SelectiveGapicGeneration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.iam.v1.AuditConfig", "queryAllDeclaredConstructors": true, @@ -3041,6 +3059,15 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.spanner.admin.instance.v1.Instance$DefaultBackupScheduleType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.spanner.admin.instance.v1.Instance$Edition", "queryAllDeclaredConstructors": true, diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 7237449e3e7..b26900ac62e 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner - 6.79.0 + 6.80.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 google-cloud-spanner diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java index 27253bf1e13..ebfb0e0a774 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractMultiplexedSessionDatabaseClient.java @@ -40,17 +40,6 @@ public String getDatabaseRole() { throw new UnsupportedOperationException(); } - @Override - public Timestamp write(Iterable mutations) throws SpannerException { - throw new UnsupportedOperationException(); - } - - @Override - public CommitResponse writeWithOptions(Iterable mutations, TransactionOption... options) - throws SpannerException { - throw new UnsupportedOperationException(); - } - @Override public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException { return writeAtLeastOnceWithOptions(mutations).getCommitTimestamp(); @@ -63,26 +52,6 @@ public ServerStream batchWriteAtLeastOnce( throw new UnsupportedOperationException(); } - @Override - public TransactionRunner readWriteTransaction(TransactionOption... options) { - throw new UnsupportedOperationException(); - } - - @Override - public TransactionManager transactionManager(TransactionOption... options) { - throw new UnsupportedOperationException(); - } - - @Override - public AsyncRunner runAsync(TransactionOption... options) { - throw new UnsupportedOperationException(); - } - - @Override - public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) { - throw new UnsupportedOperationException(); - } - @Override public long executePartitionedUpdate(Statement stmt, UpdateOption... options) { throw new UnsupportedOperationException(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java index caf0e06379e..a89090e34d9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java @@ -48,6 +48,7 @@ import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryMode; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.ReadRequest; import com.google.spanner.v1.RequestOptions; @@ -893,6 +894,13 @@ public void onDone(boolean withBeginTransaction) { this.session.onReadDone(); } + /** + * For transactions other than read-write, the MultiplexedSessionPrecommitToken will not be + * present in the RPC response. In such cases, this method will be a no-op. + */ + @Override + public void onPrecommitToken(MultiplexedSessionPrecommitToken token) {} + private ResultSet readInternal( String table, @Nullable String index, 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 2cf93fb92ec..fdc0398d5fe 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 @@ -27,6 +27,7 @@ import com.google.protobuf.ListValue; import com.google.protobuf.ProtocolMessageEnum; import com.google.protobuf.Value.KindCase; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.Transaction; import java.io.IOException; import java.io.Serializable; @@ -57,6 +58,12 @@ void onTransactionMetadata(Transaction transaction, boolean shouldIncludeId) /** Called when the read finishes normally. */ void onDone(boolean withBeginTransaction); + + /** + * Called when the RPC response contains a MultiplexedSessionPrecommitToken. A precommit token + * will be included if the read-write transaction is executed on a multiplexed session. + */ + void onPrecommitToken(MultiplexedSessionPrecommitToken token); } static final class LazyByteArray implements Serializable { 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 8b20dd824a0..0057bb15bea 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 @@ -28,6 +28,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.ByteString; /** Implementation of {@link AsyncTransactionManager}. */ final class AsyncTransactionManagerImpl @@ -66,6 +67,9 @@ public ApiFuture closeAsync() { if (txn != null) { txn.close(); } + if (session != null) { + session.onTransactionDone(); + } return MoreObjects.firstNonNull(res, ApiFutures.immediateFuture(null)); } @@ -77,7 +81,19 @@ public TransactionContextFutureImpl beginAsync() { private ApiFuture internalBeginAsync(boolean firstAttempt) { txnState = TransactionState.STARTED; - txn = session.newTransaction(options); + + // Determine the latest transactionId when using a multiplexed session. + ByteString multiplexedSessionPreviousTransactionId = ByteString.EMPTY; + if (txn != null && session.getIsMultiplexed() && !firstAttempt) { + // Use the current transactionId if available, otherwise fallback to the previous aborted + // transactionId. + multiplexedSessionPreviousTransactionId = + txn.transactionId != null ? txn.transactionId : txn.getPreviousTransactionId(); + } + + txn = + session.newTransaction( + options, /* previousTransactionId = */ multiplexedSessionPreviousTransactionId); if (firstAttempt) { session.setActive(this); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java index a7665f8556a..ef1d70eec25 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java @@ -20,6 +20,7 @@ import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_HASH_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_NAME_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_UID_KEY; +import static com.google.cloud.spanner.BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.LOCATION_ID_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.PROJECT_ID_KEY; @@ -83,6 +84,7 @@ Map createClientAttributes(String projectId, String client_name) clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation()); clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId); // TODO: Replace this with real value. + clientAttributes.put(DIRECT_PATH_ENABLED_KEY.getKey(), "false"); clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "unknown"); clientAttributes.put(CLIENT_NAME_KEY.getKey(), client_name); String clientUid = getDefaultTaskValue(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CompositeTracer.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CompositeTracer.java index 085a91fb88e..050116af8de 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CompositeTracer.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/CompositeTracer.java @@ -184,7 +184,7 @@ public void addAttributes(Map attributes) { for (ApiTracer child : children) { if (child instanceof MetricsTracer) { MetricsTracer metricsTracer = (MetricsTracer) child; - attributes.forEach((key, value) -> metricsTracer.addAttributes(key, value)); + metricsTracer.addAttributes(attributes); } } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java index 909d731818f..91edce79325 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java @@ -36,6 +36,7 @@ class DatabaseClientImpl implements DatabaseClient { @VisibleForTesting final String clientId; @VisibleForTesting final SessionPool pool; @VisibleForTesting final MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient; + @VisibleForTesting final boolean useMultiplexedSessionForRW; final boolean useMultiplexedSessionBlindWrite; @@ -46,7 +47,8 @@ class DatabaseClientImpl implements DatabaseClient { pool, /* useMultiplexedSessionBlindWrite = */ false, /* multiplexedSessionDatabaseClient = */ null, - tracer); + tracer, + /* useMultiplexedSessionForRW = */ false); } @VisibleForTesting @@ -56,7 +58,8 @@ class DatabaseClientImpl implements DatabaseClient { pool, /* useMultiplexedSessionBlindWrite = */ false, /* multiplexedSessionDatabaseClient = */ null, - tracer); + tracer, + /* useMultiplexedSessionForRW = */ false); } DatabaseClientImpl( @@ -64,12 +67,14 @@ class DatabaseClientImpl implements DatabaseClient { SessionPool pool, boolean useMultiplexedSessionBlindWrite, @Nullable MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient, - TraceWrapper tracer) { + TraceWrapper tracer, + boolean useMultiplexedSessionForRW) { this.clientId = clientId; this.pool = pool; this.useMultiplexedSessionBlindWrite = useMultiplexedSessionBlindWrite; this.multiplexedSessionDatabaseClient = multiplexedSessionDatabaseClient; this.tracer = tracer; + this.useMultiplexedSessionForRW = useMultiplexedSessionForRW; } @VisibleForTesting @@ -85,6 +90,14 @@ DatabaseClient getMultiplexedSession() { return pool.getMultiplexedSessionWithFallback(); } + @VisibleForTesting + DatabaseClient getMultiplexedSessionForRW() { + if (this.useMultiplexedSessionForRW) { + return getMultiplexedSession(); + } + return getSession(); + } + private MultiplexedSessionDatabaseClient getMultiplexedSessionDatabaseClient() { return canUseMultiplexedSessions() ? this.multiplexedSessionDatabaseClient : null; } @@ -116,6 +129,9 @@ public CommitResponse writeWithOptions( throws SpannerException { ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION, options); try (IScope s = tracer.withSpan(span)) { + if (this.useMultiplexedSessionForRW && getMultiplexedSessionDatabaseClient() != null) { + return getMultiplexedSessionDatabaseClient().writeWithOptions(mutations, options); + } return runWithSessionRetry(session -> session.writeWithOptions(mutations, options)); } catch (RuntimeException e) { span.setStatus(e); @@ -241,7 +257,7 @@ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) { public TransactionRunner readWriteTransaction(TransactionOption... options) { ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION, options); try (IScope s = tracer.withSpan(span)) { - return getSession().readWriteTransaction(options); + return getMultiplexedSessionForRW().readWriteTransaction(options); } catch (RuntimeException e) { span.setStatus(e); span.end(); @@ -253,7 +269,7 @@ public TransactionRunner readWriteTransaction(TransactionOption... options) { public TransactionManager transactionManager(TransactionOption... options) { ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION, options); try (IScope s = tracer.withSpan(span)) { - return getSession().transactionManager(options); + return getMultiplexedSessionForRW().transactionManager(options); } catch (RuntimeException e) { span.setStatus(e); span.end(); @@ -265,7 +281,7 @@ public TransactionManager transactionManager(TransactionOption... options) { public AsyncRunner runAsync(TransactionOption... options) { ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION, options); try (IScope s = tracer.withSpan(span)) { - return getSession().runAsync(options); + return getMultiplexedSessionForRW().runAsync(options); } catch (RuntimeException e) { span.setStatus(e); span.end(); @@ -277,7 +293,7 @@ public AsyncRunner runAsync(TransactionOption... options) { public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) { ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION, options); try (IScope s = tracer.withSpan(span)) { - return getSession().transactionManagerAsync(options); + return getMultiplexedSessionForRW().transactionManagerAsync(options); } catch (RuntimeException e) { span.setStatus(e); span.end(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncRunner.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncRunner.java new file mode 100644 index 00000000000..3783a84903f --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncRunner.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.cloud.Timestamp; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +/** + * Represents a {@link AsyncRunner} using a multiplexed session that is not yet ready. The execution + * will be delayed until the multiplexed session has been created and is ready. This class is only + * used during the startup of the client and the multiplexed session has not yet been created. + */ +public class DelayedAsyncRunner implements AsyncRunner { + + private final ApiFuture asyncRunnerFuture; + + public DelayedAsyncRunner(ApiFuture asyncRunnerFuture) { + this.asyncRunnerFuture = asyncRunnerFuture; + } + + ApiFuture getAsyncRunner() { + return ApiFutures.catchingAsync( + asyncRunnerFuture, + Exception.class, + exception -> { + if (exception instanceof InterruptedException) { + throw SpannerExceptionFactory.propagateInterrupt((InterruptedException) exception); + } + if (exception instanceof ExecutionException) { + throw SpannerExceptionFactory.causeAsRunTimeException((ExecutionException) exception); + } + throw exception; + }, + MoreExecutors.directExecutor()); + } + + @Override + public ApiFuture runAsync(AsyncWork work, Executor executor) { + return ApiFutures.transformAsync( + getAsyncRunner(), + asyncRunner -> asyncRunner.runAsync(work, executor), + MoreExecutors.directExecutor()); + } + + @Override + public ApiFuture getCommitTimestamp() { + return ApiFutures.transformAsync( + getAsyncRunner(), AsyncRunner::getCommitTimestamp, MoreExecutors.directExecutor()); + } + + @Override + public ApiFuture getCommitResponse() { + return ApiFutures.transformAsync( + getAsyncRunner(), AsyncRunner::getCommitResponse, MoreExecutors.directExecutor()); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncTransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncTransactionManager.java new file mode 100644 index 00000000000..56b874e4a87 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedAsyncTransactionManager.java @@ -0,0 +1,82 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.core.ApiFuture; +import com.google.cloud.spanner.TransactionManager.TransactionState; +import java.util.concurrent.ExecutionException; + +/** + * Represents a {@link AsyncTransactionManager} using a multiplexed session that is not yet ready. + * The execution will be delayed until the multiplexed session has been created and is ready. This + * class is only used during the startup of the client and the multiplexed session has not yet been + * created. + */ +public class DelayedAsyncTransactionManager implements AsyncTransactionManager { + + private final ApiFuture asyncTransactionManagerApiFuture; + + DelayedAsyncTransactionManager( + ApiFuture asyncTransactionManagerApiFuture) { + this.asyncTransactionManagerApiFuture = asyncTransactionManagerApiFuture; + } + + AsyncTransactionManager getAsyncTransactionManager() { + try { + return this.asyncTransactionManagerApiFuture.get(); + } catch (ExecutionException executionException) { + throw SpannerExceptionFactory.causeAsRunTimeException(executionException); + } catch (InterruptedException interruptedException) { + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); + } + } + + @Override + public TransactionContextFuture beginAsync() { + return getAsyncTransactionManager().beginAsync(); + } + + @Override + public ApiFuture rollbackAsync() { + return getAsyncTransactionManager().rollbackAsync(); + } + + @Override + public TransactionContextFuture resetForRetryAsync() { + return getAsyncTransactionManager().resetForRetryAsync(); + } + + @Override + public TransactionState getState() { + return getAsyncTransactionManager().getState(); + } + + @Override + public ApiFuture getCommitResponse() { + return getAsyncTransactionManager().getCommitResponse(); + } + + @Override + public void close() { + getAsyncTransactionManager().close(); + } + + @Override + public ApiFuture closeAsync() { + return getAsyncTransactionManager().closeAsync(); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java index 36750eaccd1..ad3e6b0cf70 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedMultiplexedSessionTransaction.java @@ -20,6 +20,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.cloud.Timestamp; import com.google.cloud.spanner.DelayedReadContext.DelayedReadOnlyTransaction; import com.google.cloud.spanner.MultiplexedSessionDatabaseClient.MultiplexedSessionTransaction; import com.google.cloud.spanner.Options.TransactionOption; @@ -57,7 +58,7 @@ public ReadContext singleUse() { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, true) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ true) .singleUse(), MoreExecutors.directExecutor())); } @@ -69,7 +70,7 @@ public ReadContext singleUse(TimestampBound bound) { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, true) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ true) .singleUse(bound), MoreExecutors.directExecutor())); } @@ -81,7 +82,7 @@ public ReadOnlyTransaction singleUseReadOnlyTransaction() { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, true) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ true) .singleUseReadOnlyTransaction(), MoreExecutors.directExecutor())); } @@ -93,7 +94,7 @@ public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, true) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ true) .singleUseReadOnlyTransaction(bound), MoreExecutors.directExecutor())); } @@ -105,7 +106,7 @@ public ReadOnlyTransaction readOnlyTransaction() { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, false) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) .readOnlyTransaction(), MoreExecutors.directExecutor())); } @@ -117,7 +118,7 @@ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) { this.sessionFuture, sessionReference -> new MultiplexedSessionTransaction( - client, span, sessionReference, NO_CHANNEL_HINT, false) + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) .readOnlyTransaction(bound), MoreExecutors.directExecutor())); } @@ -131,11 +132,85 @@ public CommitResponse writeAtLeastOnceWithOptions( Iterable mutations, TransactionOption... options) throws SpannerException { SessionReference sessionReference = getSessionReference(); try (MultiplexedSessionTransaction transaction = - new MultiplexedSessionTransaction(client, span, sessionReference, NO_CHANNEL_HINT, true)) { + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ true)) { return transaction.writeAtLeastOnceWithOptions(mutations, options); } } + // This is a blocking method, as the interface that it implements is also defined as a blocking + // method. + @Override + public Timestamp write(Iterable mutations) throws SpannerException { + SessionReference sessionReference = getSessionReference(); + try (MultiplexedSessionTransaction transaction = + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false)) { + return transaction.write(mutations); + } + } + + // This is a blocking method, as the interface that it implements is also defined as a blocking + // method. + @Override + public CommitResponse writeWithOptions(Iterable mutations, TransactionOption... options) + throws SpannerException { + SessionReference sessionReference = getSessionReference(); + try (MultiplexedSessionTransaction transaction = + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false)) { + return transaction.writeWithOptions(mutations, options); + } + } + + @Override + public TransactionRunner readWriteTransaction(TransactionOption... options) { + return new DelayedTransactionRunner( + ApiFutures.transform( + this.sessionFuture, + sessionReference -> + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) + .readWriteTransaction(options), + MoreExecutors.directExecutor())); + } + + @Override + public TransactionManager transactionManager(TransactionOption... options) { + return new DelayedTransactionManager( + ApiFutures.transform( + this.sessionFuture, + sessionReference -> + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) + .transactionManager(options), + MoreExecutors.directExecutor())); + } + + @Override + public AsyncRunner runAsync(TransactionOption... options) { + return new DelayedAsyncRunner( + ApiFutures.transform( + this.sessionFuture, + sessionReference -> + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) + .runAsync(options), + MoreExecutors.directExecutor())); + } + + @Override + public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) { + return new DelayedAsyncTransactionManager( + ApiFutures.transform( + this.sessionFuture, + sessionReference -> + new MultiplexedSessionTransaction( + client, span, sessionReference, NO_CHANNEL_HINT, /* singleUse = */ false) + .transactionManagerAsync(options), + MoreExecutors.directExecutor())); + } + /** * Gets the session reference that this delayed transaction is waiting for. This method should * only be called by methods that are allowed to be blocking. @@ -144,12 +219,7 @@ private SessionReference getSessionReference() { try { return this.sessionFuture.get(); } catch (ExecutionException executionException) { - // Propagate the underlying exception as a RuntimeException (SpannerException is also a - // RuntimeException). - if (executionException.getCause() instanceof RuntimeException) { - throw (RuntimeException) executionException.getCause(); - } - throw SpannerExceptionFactory.asSpannerException(executionException.getCause()); + throw SpannerExceptionFactory.causeAsRunTimeException(executionException); } catch (InterruptedException interruptedException) { throw SpannerExceptionFactory.propagateInterrupt(interruptedException); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java index 62bc5711852..86d6ae079d3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedReadContext.java @@ -44,12 +44,7 @@ T getReadContext() { try { return this.readContextFuture.get(); } catch (ExecutionException executionException) { - // Propagate the underlying exception as a RuntimeException (SpannerException is also a - // RuntimeException). - if (executionException.getCause() instanceof RuntimeException) { - throw (RuntimeException) executionException.getCause(); - } - throw SpannerExceptionFactory.asSpannerException(executionException.getCause()); + throw SpannerExceptionFactory.causeAsRunTimeException(executionException); } catch (InterruptedException interruptedException) { throw SpannerExceptionFactory.propagateInterrupt(interruptedException); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionManager.java new file mode 100644 index 00000000000..29eae6477fc --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionManager.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.core.ApiFuture; +import com.google.cloud.Timestamp; +import java.util.concurrent.ExecutionException; + +/** + * Represents a {@link TransactionManager} using a multiplexed session that is not yet ready. The + * execution will be delayed until the multiplexed session has been created and is ready. This class + * is only used during the startup of the client and the multiplexed session has not yet been + * created. + */ +class DelayedTransactionManager implements TransactionManager { + + private final ApiFuture transactionManagerFuture; + + DelayedTransactionManager(ApiFuture transactionManagerFuture) { + this.transactionManagerFuture = transactionManagerFuture; + } + + TransactionManager getTransactionManager() { + try { + return this.transactionManagerFuture.get(); + } catch (ExecutionException executionException) { + throw SpannerExceptionFactory.causeAsRunTimeException(executionException); + } catch (InterruptedException interruptedException) { + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); + } + } + + @Override + public TransactionContext begin() { + return getTransactionManager().begin(); + } + + @Override + public void commit() { + getTransactionManager().commit(); + } + + @Override + public void rollback() { + getTransactionManager().rollback(); + } + + @Override + public TransactionContext resetForRetry() { + return getTransactionManager().resetForRetry(); + } + + @Override + public Timestamp getCommitTimestamp() { + return getTransactionManager().getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + return getTransactionManager().getCommitResponse(); + } + + @Override + public TransactionState getState() { + return getTransactionManager().getState(); + } + + @Override + public void close() { + getTransactionManager().close(); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionRunner.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionRunner.java new file mode 100644 index 00000000000..bf0ed1b880a --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DelayedTransactionRunner.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.core.ApiFuture; +import com.google.cloud.Timestamp; +import java.util.concurrent.ExecutionException; +import javax.annotation.Nullable; + +/** + * Represents a {@link TransactionRunner} using a multiplexed session that is not yet ready. The + * execution will be delayed until the multiplexed session has been created and is ready. This class + * is only used during the startup of the client and the multiplexed session has not yet been + * created. + */ +class DelayedTransactionRunner implements TransactionRunner { + private final ApiFuture transactionRunnerFuture; + + DelayedTransactionRunner(ApiFuture transactionRunnerFuture) { + this.transactionRunnerFuture = transactionRunnerFuture; + } + + TransactionRunner getTransactionRunner() { + try { + return this.transactionRunnerFuture.get(); + } catch (ExecutionException executionException) { + throw SpannerExceptionFactory.causeAsRunTimeException(executionException); + } catch (InterruptedException interruptedException) { + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); + } + } + + @Nullable + @Override + public T run(TransactionCallable callable) { + return getTransactionRunner().run(callable); + } + + @Override + public Timestamp getCommitTimestamp() { + return getTransactionRunner().getCommitTimestamp(); + } + + @Override + public CommitResponse getCommitResponse() { + return getTransactionRunner().getCommitResponse(); + } + + @Override + public TransactionRunner allowNestedTransaction() { + return getTransactionRunner().allowNestedTransaction(); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java index be75c1e5c4e..23c9dd7c2d3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java @@ -47,7 +47,7 @@ class GrpcResultSet extends AbstractResultSet> implements ProtobufR GrpcResultSet( CloseableIterator iterator, Listener listener, DecodeMode decodeMode) { - this.iterator = new GrpcValueIterator(iterator); + this.iterator = new GrpcValueIterator(iterator, listener); this.listener = listener; this.decodeMode = decodeMode; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcValueIterator.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcValueIterator.java index 0a2e17bd2b5..1a3df8b9123 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcValueIterator.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcValueIterator.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import com.google.cloud.spanner.AbstractResultSet.CloseableIterator; +import com.google.cloud.spanner.AbstractResultSet.Listener; import com.google.common.collect.AbstractIterator; import com.google.protobuf.ListValue; import com.google.protobuf.Value.KindCase; @@ -44,9 +45,11 @@ private enum StreamValue { private PartialResultSet current; private int pos; private ResultSetStats statistics; + private final Listener listener; - GrpcValueIterator(CloseableIterator stream) { + GrpcValueIterator(CloseableIterator stream, Listener listener) { this.stream = stream; + this.listener = listener; } @SuppressWarnings("unchecked") @@ -154,6 +157,10 @@ private boolean ensureReady(StreamValue requiredValue) throws SpannerException { ErrorCode.INTERNAL, "Invalid type metadata: " + e.getMessage(), e); } } + // collect the precommit token from each PartialResultSet + if (current.hasPrecommitToken()) { + listener.onPrecommitToken(current.getPrecommitToken()); + } if (current.hasStats()) { statistics = current.getStats(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java index 81415e80d25..0c20c4cbc76 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java @@ -21,6 +21,7 @@ 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.Options.TransactionOption; import com.google.cloud.spanner.SessionClient.SessionConsumer; import com.google.cloud.spanner.SpannerException.ResourceNotFoundException; @@ -367,41 +368,78 @@ private int getSingleUseChannelHint() { } } + @Override + public Timestamp write(Iterable mutations) throws SpannerException { + return createMultiplexedSessionTransaction(/* singleUse = */ false).write(mutations); + } + + @Override + public CommitResponse writeWithOptions( + final Iterable mutations, final TransactionOption... options) + throws SpannerException { + return createMultiplexedSessionTransaction(/* singleUse = */ false) + .writeWithOptions(mutations, options); + } + @Override public CommitResponse writeAtLeastOnceWithOptions( Iterable mutations, TransactionOption... options) throws SpannerException { - return createMultiplexedSessionTransaction(true) + return createMultiplexedSessionTransaction(/* singleUse = */ true) .writeAtLeastOnceWithOptions(mutations, options); } @Override public ReadContext singleUse() { - return createMultiplexedSessionTransaction(true).singleUse(); + return createMultiplexedSessionTransaction(/* singleUse = */ true).singleUse(); } @Override public ReadContext singleUse(TimestampBound bound) { - return createMultiplexedSessionTransaction(true).singleUse(bound); + return createMultiplexedSessionTransaction(/* singleUse = */ true).singleUse(bound); } @Override public ReadOnlyTransaction singleUseReadOnlyTransaction() { - return createMultiplexedSessionTransaction(true).singleUseReadOnlyTransaction(); + return createMultiplexedSessionTransaction(/* singleUse = */ true) + .singleUseReadOnlyTransaction(); } @Override public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) { - return createMultiplexedSessionTransaction(true).singleUseReadOnlyTransaction(bound); + return createMultiplexedSessionTransaction(/* singleUse = */ true) + .singleUseReadOnlyTransaction(bound); } @Override public ReadOnlyTransaction readOnlyTransaction() { - return createMultiplexedSessionTransaction(false).readOnlyTransaction(); + return createMultiplexedSessionTransaction(/* singleUse = */ false).readOnlyTransaction(); } @Override public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) { - return createMultiplexedSessionTransaction(false).readOnlyTransaction(bound); + return createMultiplexedSessionTransaction(/* singleUse = */ false).readOnlyTransaction(bound); + } + + @Override + public TransactionRunner readWriteTransaction(TransactionOption... options) { + return createMultiplexedSessionTransaction(/* singleUse = */ false) + .readWriteTransaction(options); + } + + @Override + public TransactionManager transactionManager(TransactionOption... options) { + return createMultiplexedSessionTransaction(/* singleUse = */ false).transactionManager(options); + } + + @Override + public AsyncRunner runAsync(TransactionOption... options) { + return createMultiplexedSessionTransaction(/* singleUse = */ false).runAsync(options); + } + + @Override + public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) { + return createMultiplexedSessionTransaction(/* singleUse = */ false) + .transactionManagerAsync(options); } /** 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 7b9abc71a85..60c9d45d186 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 @@ -69,7 +69,8 @@ static void throwIfTransactionsPending() { } } - static TransactionOptions createReadWriteTransactionOptions(Options options) { + static TransactionOptions createReadWriteTransactionOptions( + Options options, ByteString previousTransactionId) { TransactionOptions.Builder transactionOptions = TransactionOptions.newBuilder(); if (options.withExcludeTxnFromChangeStreams() == Boolean.TRUE) { transactionOptions.setExcludeTxnFromChangeStreams(true); @@ -78,6 +79,10 @@ static TransactionOptions createReadWriteTransactionOptions(Options options) { if (options.withOptimisticLock() == Boolean.TRUE) { readWrite.setReadLockMode(TransactionOptions.ReadWrite.ReadLockMode.OPTIMISTIC); } + if (previousTransactionId != null + && previousTransactionId != com.google.protobuf.ByteString.EMPTY) { + readWrite.setMultiplexedSessionPreviousTransactionId(previousTransactionId); + } transactionOptions.setReadWrite(readWrite); return transactionOptions.build(); } @@ -427,13 +432,17 @@ public void close() { } ApiFuture beginTransactionAsync( - Options transactionOptions, boolean routeToLeader, Map channelHint) { + Options transactionOptions, + boolean routeToLeader, + Map channelHint, + ByteString previousTransactionId) { final SettableApiFuture res = SettableApiFuture.create(); final ISpan span = tracer.spanBuilder(SpannerImpl.BEGIN_TRANSACTION); final BeginTransactionRequest request = BeginTransactionRequest.newBuilder() .setSession(getName()) - .setOptions(createReadWriteTransactionOptions(transactionOptions)) + .setOptions( + createReadWriteTransactionOptions(transactionOptions, previousTransactionId)) .build(); final ApiFuture requestFuture; try (IScope ignore = tracer.withSpan(span)) { @@ -469,11 +478,12 @@ ApiFuture beginTransactionAsync( return res; } - TransactionContextImpl newTransaction(Options options) { + TransactionContextImpl newTransaction(Options options, ByteString previousTransactionId) { return TransactionContextImpl.newBuilder() .setSession(this) .setOptions(options) .setTransactionId(null) + .setPreviousTransactionId(previousTransactionId) .setOptions(options) .setTrackTransactionStarter(spanner.getOptions().isTrackTransactionStarter()) .setRpc(spanner.getRpc()) 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 ba335cf8f9f..89de8df3ca9 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 @@ -822,6 +822,8 @@ Builder setUseMultiplexedSessionBlindWrite(boolean useMultiplexedSessionBlindWri * Sets whether the client should use multiplexed session for R/W operations or not. This method * is intentionally package-private and intended for internal use. */ + @InternalApi + @VisibleForTesting Builder setUseMultiplexedSessionForRW(boolean useMultiplexedSessionForRW) { this.useMultiplexedSessionForRW = useMultiplexedSessionForRW; return this; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java index 0a7158deace..b672b3b8fa4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java @@ -31,6 +31,7 @@ import io.grpc.StatusRuntimeException; import io.grpc.protobuf.ProtoUtils; import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; @@ -188,6 +189,15 @@ public static SpannerException newSpannerException(@Nullable Context context, Th return newSpannerException(ErrorCode.fromGrpcStatus(status), cause.getMessage(), cause); } + public static RuntimeException causeAsRunTimeException(ExecutionException executionException) { + // Propagate the underlying exception as a RuntimeException (SpannerException is also a + // RuntimeException). + if (executionException.getCause() instanceof RuntimeException) { + throw (RuntimeException) executionException.getCause(); + } + throw asSpannerException(executionException.getCause()); + } + /** * Creates a new SpannerException that indicates that the RPC or transaction should be retried on * a different gRPC channel. This is an experimental feature that can be removed in the future. 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 e5982cba0c8..dac1fc2c82b 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 @@ -307,7 +307,8 @@ public DatabaseClient getDatabaseClient(DatabaseId db) { clientId, pool, getOptions().getSessionPoolOptions().getUseMultiplexedSessionBlindWrite(), - multiplexedSessionDatabaseClient); + multiplexedSessionDatabaseClient, + useMultiplexedSessionForRW); dbClients.put(db, dbClient); return dbClient; } @@ -319,9 +320,15 @@ DatabaseClientImpl createDatabaseClient( String clientId, SessionPool pool, boolean useMultiplexedSessionBlindWrite, - @Nullable MultiplexedSessionDatabaseClient multiplexedSessionClient) { + @Nullable MultiplexedSessionDatabaseClient multiplexedSessionClient, + boolean useMultiplexedSessionForRW) { return new DatabaseClientImpl( - clientId, pool, useMultiplexedSessionBlindWrite, multiplexedSessionClient, tracer); + clientId, + pool, + useMultiplexedSessionBlindWrite, + multiplexedSessionClient, + tracer, + useMultiplexedSessionForRW); } @Override diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 5756ff64b89..d7ba20b2d1b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -1726,14 +1726,13 @@ private ApiTracerFactory getDefaultApiTracerFactory() { private ApiTracerFactory createMetricsApiTracerFactory() { OpenTelemetry openTelemetry = this.builtInOpenTelemetryMetricsProvider.getOrCreateOpenTelemetry( - getDefaultProjectId(), getCredentials()); + this.getProjectId(), getCredentials()); return openTelemetry != null ? new MetricsTracerFactory( new OpenTelemetryMetricsRecorder(openTelemetry, BuiltInMetricsConstant.METER_NAME), builtInOpenTelemetryMetricsProvider.createClientAttributes( - getDefaultProjectId(), - "spanner-java/" + GaxProperties.getLibraryVersion(getClass()))) + this.getProjectId(), "spanner-java/" + GaxProperties.getLibraryVersion(getClass()))) : null; } 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 95ffd1168b2..cafb27ba6b7 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 @@ -20,6 +20,7 @@ import com.google.cloud.spanner.Options.TransactionOption; import com.google.cloud.spanner.SessionImpl.SessionTransaction; import com.google.common.base.Preconditions; +import com.google.protobuf.ByteString; /** Implementation of {@link TransactionManager}. */ final class TransactionManagerImpl implements TransactionManager, SessionTransaction { @@ -53,7 +54,7 @@ public void setSpan(ISpan span) { public TransactionContext begin() { Preconditions.checkState(txn == null, "begin can only be called once"); try (IScope s = tracer.withSpan(span)) { - txn = session.newTransaction(options); + txn = session.newTransaction(options, /* previousTransactionId = */ ByteString.EMPTY); session.setActive(this); txnState = TransactionState.STARTED; return txn; @@ -102,7 +103,18 @@ public TransactionContext resetForRetry() { } try (IScope s = tracer.withSpan(span)) { boolean useInlinedBegin = txn.transactionId != null; - txn = session.newTransaction(options); + + // Determine the latest transactionId when using a multiplexed session. + ByteString multiplexedSessionPreviousTransactionId = ByteString.EMPTY; + if (session.getIsMultiplexed()) { + // Use the current transactionId if available, otherwise fallback to the previous aborted + // transactionId. + multiplexedSessionPreviousTransactionId = + txn.transactionId != null ? txn.transactionId : txn.getPreviousTransactionId(); + } + txn = + session.newTransaction( + options, /* previousTransactionId = */ multiplexedSessionPreviousTransactionId); if (!useInlinedBegin) { txn.ensureTxn(); } @@ -136,6 +148,7 @@ public void close() { } } finally { span.end(); + session.onTransactionDone(); } } 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 c8bf6dc833b..92d9c50aa9e 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 @@ -46,6 +46,7 @@ import com.google.spanner.v1.ExecuteBatchDmlResponse; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryMode; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.RequestOptions; import com.google.spanner.v1.ResultSet; import com.google.spanner.v1.ResultSetStats; @@ -93,6 +94,9 @@ static class Builder extends AbstractReadContext.Builder finishedAsyncOperations = SettableApiFuture.create(); @@ -201,6 +215,8 @@ public void removeListener(Runnable listener) { volatile ByteString transactionId; + final ByteString previousTransactionId; + private CommitResponse commitResponse; private final Clock clock; @@ -216,6 +232,7 @@ private TransactionContextImpl(Builder builder) { this.channelHint = getChannelHintOptions( session.getOptions(), ThreadLocalRandom.current().nextLong(Long.MAX_VALUE)); + this.previousTransactionId = builder.previousTransactionId; } @Override @@ -246,6 +263,10 @@ private void decreaseAsyncOperations() { } } + ByteString getPreviousTransactionId() { + return this.previousTransactionId; + } + @Override public void close() { // Only mark the context as closed, but do not end the tracer span, as that is done by the @@ -283,7 +304,8 @@ ApiFuture ensureTxnAsync() { private void createTxnAsync(final SettableApiFuture res) { span.addAnnotation("Creating Transaction"); final ApiFuture fut = - session.beginTransactionAsync(options, isRouteToLeader(), getTransactionChannelHint()); + session.beginTransactionAsync( + options, isRouteToLeader(), getTransactionChannelHint(), getPreviousTransactionId()); fut.addListener( () -> { try { @@ -423,6 +445,10 @@ public void run() { } requestBuilder.setRequestOptions(requestOptionsBuilder.build()); } + if (session.getIsMultiplexed() && getLatestPrecommitToken() != null) { + // Set the precommit token in the CommitRequest for multiplexed sessions. + requestBuilder.setPrecommitToken(getLatestPrecommitToken()); + } final CommitRequest commitRequest = requestBuilder.build(); span.addAnnotation("Starting Commit"); final ApiFuture commitFuture; @@ -558,7 +584,9 @@ TransactionSelector getTransactionSelector() { } if (tx == null) { return TransactionSelector.newBuilder() - .setBegin(SessionImpl.createReadWriteTransactionOptions(options)) + .setBegin( + SessionImpl.createReadWriteTransactionOptions( + options, getPreviousTransactionId())) .build(); } else { // Wait for the transaction to come available. The tx.get() call will fail with an @@ -625,6 +653,25 @@ public void onTransactionMetadata(Transaction transaction, boolean shouldInclude } } + /** + * In read-write transactions, the precommit token with the highest sequence number from this + * transaction attempt will be tracked and included in the + * [Commit][google.spanner.v1.Spanner.Commit] request for the transaction. + */ + @Override + public void onPrecommitToken(MultiplexedSessionPrecommitToken token) { + if (token == null) { + return; + } + synchronized (precommitTokenLock) { + if (this.latestPrecommitToken == null + || token.getSeqNum() > this.latestPrecommitToken.getSeqNum()) { + this.latestPrecommitToken = token; + txnLogger.log(Level.FINE, "Updating precommit token to " + this.latestPrecommitToken); + } + } + } + @Nullable String getTransactionTag() { if (this.options.hasTag()) { @@ -633,6 +680,13 @@ String getTransactionTag() { return null; } + @Nullable + MultiplexedSessionPrecommitToken getLatestPrecommitToken() { + synchronized (precommitTokenLock) { + return this.latestPrecommitToken; + } + } + @Override public SpannerException onError(SpannerException e, boolean withBeginTransaction) { e = super.onError(e, withBeginTransaction); @@ -811,6 +865,9 @@ private ResultSet internalExecuteUpdate( throw new IllegalArgumentException( "DML response missing stats possibly due to non-DML statement as input"); } + if (resultSet.hasPrecommitToken()) { + onPrecommitToken(resultSet.getPrecommitToken()); + } return resultSet; } catch (Throwable t) { throw onError( @@ -885,6 +942,9 @@ public ApiFuture executeUpdateAsync(Statement statement, UpdateOption... u resultSet.get().getMetadata().getTransaction(), builder.getTransaction().hasBegin()); } + if (resultSet.get().hasPrecommitToken()) { + onPrecommitToken(resultSet.get().getPrecommitToken()); + } } catch (Throwable e) { // Ignore this error here as it is handled by the future that is returned by the // executeUpdateAsync method. @@ -940,6 +1000,10 @@ public long[] batchUpdate(Iterable statements, UpdateOption... update } } + if (response.hasPrecommitToken()) { + onPrecommitToken(response.getPrecommitToken()); + } + // If one of the DML statements was aborted, we should throw an aborted exception. // In all other cases, we should throw a BatchUpdateException. if (response.getStatus().getCode() == Code.ABORTED_VALUE) { @@ -1004,6 +1068,9 @@ public ApiFuture batchUpdateAsync( builder.getTransaction().hasBegin()); } } + if (batchDmlResponse.hasPrecommitToken()) { + onPrecommitToken(batchDmlResponse.getPrecommitToken()); + } // If one of the DML statements was aborted, we should throw an aborted exception. // In all other cases, we should throw a BatchUpdateException. if (batchDmlResponse.getStatus().getCode() == Code.ABORTED_VALUE) { @@ -1079,7 +1146,7 @@ public TransactionRunner allowNestedTransaction() { TransactionRunnerImpl(SessionImpl session, TransactionOption... options) { this.session = session; this.options = Options.fromTransactionOptions(options); - this.txn = session.newTransaction(this.options); + this.txn = session.newTransaction(this.options, /* previousTransactionId = */ ByteString.EMPTY); this.tracer = session.getTracer(); } @@ -1105,6 +1172,7 @@ public T run(TransactionCallable callable) { // was running. SessionImpl.hasPendingTransaction.remove(); span.end(); + session.onTransactionDone(); } } @@ -1117,7 +1185,19 @@ private T runInternal(final TransactionCallable txCallable) { // Do not inline the BeginTransaction during a retry if the initial attempt did not // actually start a transaction. useInlinedBegin = txn.transactionId != null; - txn = session.newTransaction(options); + + // Determine the latest transactionId when using a multiplexed session. + ByteString multiplexedSessionPreviousTransactionId = ByteString.EMPTY; + if (session.getIsMultiplexed()) { + // Use the current transactionId if available, otherwise fallback to the previous + // transactionId. + multiplexedSessionPreviousTransactionId = + txn.transactionId != null ? txn.transactionId : txn.getPreviousTransactionId(); + } + + txn = + session.newTransaction( + options, /* previousTransactionId = */ multiplexedSessionPreviousTransactionId); } checkState( isValid, "TransactionRunner has been invalidated by a new operation on the session"); 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 dcc4c663bb3..66b09c35afd 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 @@ -26,6 +26,7 @@ import static com.google.cloud.spanner.connection.ConnectionProperties.DATA_BOOST_ENABLED; import static com.google.cloud.spanner.connection.ConnectionProperties.DIALECT; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_API_TRACING; +import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_END_TO_END_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING; import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS; import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT; @@ -249,6 +250,7 @@ public String[] getValidValues() { static final int DEFAULT_MAX_PARTITIONED_PARALLELISM = 1; static final Boolean DEFAULT_ENABLE_EXTENDED_TRACING = null; static final Boolean DEFAULT_ENABLE_API_TRACING = null; + static final boolean DEFAULT_ENABLE_END_TO_END_TRACING = false; static final boolean DEFAULT_AUTO_BATCH_DML = false; static final long DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT = 1L; static final boolean DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION = true; @@ -335,6 +337,7 @@ public String[] getValidValues() { public static final String ENABLE_EXTENDED_TRACING_PROPERTY_NAME = "enableExtendedTracing"; public static final String ENABLE_API_TRACING_PROPERTY_NAME = "enableApiTracing"; + public static final String ENABLE_END_TO_END_TRACING_PROPERTY_NAME = "enableEndToEndTracing"; public static final String AUTO_BATCH_DML_PROPERTY_NAME = "auto_batch_dml"; public static final String AUTO_BATCH_DML_UPDATE_COUNT_PROPERTY_NAME = @@ -537,7 +540,14 @@ static boolean isEnableTransactionalConnectionStateForPostgreSQL() { + "to get a detailed view of each RPC that is being executed by your application, " + "or if you want to debug potential latency problems caused by RPCs that are " + "being retried.", - DEFAULT_ENABLE_API_TRACING)))); + DEFAULT_ENABLE_API_TRACING), + ConnectionProperty.createBooleanProperty( + ENABLE_END_TO_END_TRACING_PROPERTY_NAME, + "Enable end-to-end tracing (true/false) to generate traces for both the time " + + "that is spent in the client, as well as time that is spent in the Spanner server. " + + "Server side traces can only go to Google Cloud Trace, so to see end to end traces, " + + "the application should configure an exporter that exports the traces to Google Cloud Trace.", + DEFAULT_ENABLE_END_TO_END_TRACING)))); private static final Set INTERNAL_PROPERTIES = Collections.unmodifiableSet( @@ -1205,6 +1215,11 @@ public boolean isRouteToLeader() { return getInitialConnectionPropertyValue(ROUTE_TO_LEADER); } + /** Whether end-to-end tracing is enabled. */ + public boolean isEndToEndTracingEnabled() { + return getInitialConnectionPropertyValue(ENABLE_END_TO_END_TRACING); + } + /** * The initial retryAbortsInternally value for connections created by this {@link * ConnectionOptions} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java index c62571c6c03..0ca9b7256e2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java @@ -39,6 +39,7 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DDL_IN_TRANSACTION_MODE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_API_TRACING; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_END_TO_END_TRACING; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_EXTENDED_TRACING; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_KEEP_TRANSACTION_ALIVE; @@ -65,6 +66,7 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DIALECT_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_API_TRACING_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_END_TO_END_TRACING_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME; @@ -292,6 +294,16 @@ class ConnectionProperties { DEFAULT_ENABLE_API_TRACING, BooleanConverter.INSTANCE, Context.STARTUP); + static final ConnectionProperty ENABLE_END_TO_END_TRACING = + create( + ENABLE_END_TO_END_TRACING_PROPERTY_NAME, + "Enable end-to-end tracing (true/false) to generate traces for both the time " + + "that is spent in the client, as well as time that is spent in the Spanner server. " + + "Server side traces can only go to Google Cloud Trace, so to see end to end traces, " + + "the application should configure an exporter that exports the traces to Google Cloud Trace.", + DEFAULT_ENABLE_END_TO_END_TRACING, + BooleanConverter.INSTANCE, + Context.STARTUP); static final ConnectionProperty MIN_SESSIONS = create( MIN_SESSIONS_PROPERTY_NAME, 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 246d340b070..81246e41938 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 @@ -160,6 +160,7 @@ static class SpannerPoolKey { private final OpenTelemetry openTelemetry; private final Boolean enableExtendedTracing; private final Boolean enableApiTracing; + private final boolean enableEndToEndTracing; @VisibleForTesting static SpannerPoolKey of(ConnectionOptions options) { @@ -190,6 +191,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { this.openTelemetry = options.getOpenTelemetry(); this.enableExtendedTracing = options.isEnableExtendedTracing(); this.enableApiTracing = options.isEnableApiTracing(); + this.enableEndToEndTracing = options.isEndToEndTracingEnabled(); } @Override @@ -211,7 +213,8 @@ public boolean equals(Object o) { this.useVirtualGrpcTransportThreads, other.useVirtualGrpcTransportThreads) && Objects.equals(this.openTelemetry, other.openTelemetry) && Objects.equals(this.enableExtendedTracing, other.enableExtendedTracing) - && Objects.equals(this.enableApiTracing, other.enableApiTracing); + && Objects.equals(this.enableApiTracing, other.enableApiTracing) + && Objects.equals(this.enableEndToEndTracing, other.enableEndToEndTracing); } @Override @@ -229,7 +232,8 @@ public int hashCode() { this.useVirtualGrpcTransportThreads, this.openTelemetry, this.enableExtendedTracing, - this.enableApiTracing); + this.enableApiTracing, + this.enableEndToEndTracing); } } @@ -380,6 +384,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { if (!options.isRouteToLeader()) { builder.disableLeaderAwareRouting(); } + if (options.isEndToEndTracingEnabled()) { + builder.setEnableEndToEndTracing(true); + } if (key.usePlainText) { // Credentials may not be sent over a plain text channel. builder.setCredentials(NoCredentials.getInstance()); diff --git a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.database.v1/reflect-config.json b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.database.v1/reflect-config.json index c7f99fa6ead..15e53bae299 100644 --- a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.database.v1/reflect-config.json +++ b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.database.v1/reflect-config.json @@ -395,6 +395,24 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.api.SelectiveGapicGeneration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.SelectiveGapicGeneration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.iam.v1.AuditConfig", "queryAllDeclaredConstructors": true, diff --git a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.instance.v1/reflect-config.json b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.instance.v1/reflect-config.json index cfe0908272e..1a2d3dd10d9 100644 --- a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.instance.v1/reflect-config.json +++ b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.admin.instance.v1/reflect-config.json @@ -395,6 +395,24 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.api.SelectiveGapicGeneration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.SelectiveGapicGeneration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.iam.v1.AuditConfig", "queryAllDeclaredConstructors": true, @@ -1952,6 +1970,15 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.spanner.admin.instance.v1.Instance$DefaultBackupScheduleType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.spanner.admin.instance.v1.Instance$Edition", "queryAllDeclaredConstructors": true, diff --git a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.v1/reflect-config.json b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.v1/reflect-config.json index ffa070f1225..bcbb239a5be 100644 --- a/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.v1/reflect-config.json +++ b/google-cloud-spanner/src/main/resources/META-INF/native-image/com.google.cloud.spanner.v1/reflect-config.json @@ -395,6 +395,24 @@ "allDeclaredClasses": true, "allPublicClasses": true }, + { + "name": "com.google.api.SelectiveGapicGeneration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.SelectiveGapicGeneration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, { "name": "com.google.protobuf.Any", "queryAllDeclaredConstructors": true, 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 index 08d22dd2d67..006a926e907 100644 --- 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 @@ -16,12 +16,18 @@ package com.google.cloud.spanner; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; 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 com.google.protobuf.ByteString; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Scope; import org.junit.Test; @@ -42,7 +48,7 @@ public void testCommitReturnsCommitStats() { when(oTspan.makeCurrent()).thenReturn(mock(Scope.class)); try (AsyncTransactionManagerImpl manager = new AsyncTransactionManagerImpl(session, span, Options.commitStats())) { - when(session.newTransaction(Options.fromTransactionOptions(Options.commitStats()))) + when(session.newTransaction(eq(Options.fromTransactionOptions(Options.commitStats())), any())) .thenReturn(transaction); when(transaction.ensureTxnAsync()).thenReturn(ApiFutures.immediateFuture(null)); Timestamp commitTimestamp = Timestamp.ofTimeMicroseconds(1); @@ -54,4 +60,67 @@ public void testCommitReturnsCommitStats() { verify(transaction).commitAsync(); } } + + @Test + public void testRetryUsesPreviousTransactionIdOnMultiplexedSession() { + // Set up mock transaction IDs + final ByteString mockTransactionId = ByteString.copyFromUtf8("mockTransactionId"); + final ByteString mockPreviousTransactionId = + ByteString.copyFromUtf8("mockPreviousTransactionId"); + + Span oTspan = mock(Span.class); + ISpan span = new OpenTelemetrySpan(oTspan); + when(oTspan.makeCurrent()).thenReturn(mock(Scope.class)); + // Mark the session as multiplexed. + when(session.getIsMultiplexed()).thenReturn(true); + + // Initialize a mock transaction with transactionId = null, previousTransactionId = null. + transaction = mock(TransactionRunnerImpl.TransactionContextImpl.class); + when(transaction.ensureTxnAsync()).thenReturn(ApiFutures.immediateFuture(null)); + when(session.newTransaction(eq(Options.fromTransactionOptions(Options.commitStats())), any())) + .thenReturn(transaction); + + // Simulate an ABORTED error being thrown when `commitAsync()` is called. + doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "")) + .when(transaction) + .commitAsync(); + + try (AsyncTransactionManagerImpl manager = + new AsyncTransactionManagerImpl(session, span, Options.commitStats())) { + manager.beginAsync(); + + // Verify that for the first transaction attempt, the `previousTransactionId` is + // ByteString.EMPTY. + // This is because no transaction has been previously aborted at this point. + verify(session) + .newTransaction(Options.fromTransactionOptions(Options.commitStats()), ByteString.EMPTY); + assertThrows(AbortedException.class, manager::commitAsync); + clearInvocations(session); + + // Mock the transaction object to contain transactionID=null and + // previousTransactionId=mockPreviousTransactionId + when(transaction.getPreviousTransactionId()).thenReturn(mockPreviousTransactionId); + manager.resetForRetryAsync(); + // Verify that in the first retry attempt, the `previousTransactionId` + // (mockPreviousTransactionId) is passed to the new transaction. + // This allows Spanner to retry the transaction using the ID of the aborted transaction. + verify(session) + .newTransaction( + Options.fromTransactionOptions(Options.commitStats()), mockPreviousTransactionId); + assertThrows(AbortedException.class, manager::commitAsync); + clearInvocations(session); + + // Mock the transaction object to contain transactionID=mockTransactionId and + // previousTransactionId=mockPreviousTransactionId and transactionID = null + transaction.transactionId = mockTransactionId; + manager.resetForRetryAsync(); + // Verify that the latest `transactionId` (mockTransactionId) is used in the retry. + // This ensures the retry logic is working as expected with the latest transaction ID. + verify(session) + .newTransaction(Options.fromTransactionOptions(Options.commitStats()), mockTransactionId); + + when(transaction.rollbackAsync()).thenReturn(ApiFutures.immediateFuture(null)); + manager.closeAsync(); + } + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java index 62336163eaf..59a18a3ab79 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java @@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; import com.google.spanner.v1.ExecuteSqlRequest.QueryMode; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.QueryPlan; import com.google.spanner.v1.ResultSetMetadata; @@ -77,6 +78,9 @@ public SpannerException onError(SpannerException e, boolean withBeginTransaction @Override public void onDone(boolean withBeginTransaction) {} + + @Override + public void onPrecommitToken(MultiplexedSessionPrecommitToken token) {} } @Before diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java index 7627ed54883..6deca476fc3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java @@ -50,7 +50,8 @@ DatabaseClientImpl createDatabaseClient( String clientId, SessionPool pool, boolean useMultiplexedSessionBlindWriteIgnore, - MultiplexedSessionDatabaseClient ignore) { + MultiplexedSessionDatabaseClient ignore, + boolean useMultiplexedSessionForRWIgnore) { return new DatabaseClientWithClosedSessionImpl(clientId, pool, tracer); } } 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 9f0a2822d87..014ab7e94e3 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 @@ -54,6 +54,7 @@ import com.google.spanner.v1.GetSessionRequest; import com.google.spanner.v1.ListSessionsRequest; import com.google.spanner.v1.ListSessionsResponse; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.Partition; import com.google.spanner.v1.PartitionOptions; @@ -197,10 +198,15 @@ private static class PartialResultSetsIterator implements Iterator transactionCounters = new ConcurrentHashMap<>(); private ConcurrentMap> partitionTokens = new ConcurrentHashMap<>(); private ConcurrentMap transactionLastUsed = new ConcurrentHashMap<>(); + + // Stores the latest sequence number needed for the precommit token. + // The transaction entry is created only if the transaction is read-write and executed on a + // multiplexed session. + private static ConcurrentMap transactionSequenceNo = + new ConcurrentHashMap<>(); private int maxNumSessionsInOneBatch = 100; private int maxTotalSessions = Integer.MAX_VALUE; private Iterable batchWriteResult = new ArrayList<>(); @@ -1020,7 +1035,11 @@ public void executeSql(ExecuteSqlRequest request, StreamObserver resp throw result.getException(); case RESULT_SET: returnResultSet( - result.getResultSet(), transactionId, request.getTransaction(), responseObserver); + result.getResultSet(), + transactionId, + request.getTransaction(), + responseObserver, + session); break; case UPDATE_COUNT: if (isPartitionedDmlTransaction(transactionId)) { @@ -1033,7 +1052,7 @@ public void executeSql(ExecuteSqlRequest request, StreamObserver resp .build()) .build()); } else { - responseObserver.onNext( + ResultSet.Builder resultSetBuilder = ResultSet.newBuilder() .setStats( ResultSetStats.newBuilder() @@ -1045,8 +1064,11 @@ public void executeSql(ExecuteSqlRequest request, StreamObserver resp ignoreNextInlineBeginRequest.getAndSet(false) ? Transaction.getDefaultInstance() : Transaction.newBuilder().setId(transactionId).build()) - .build()) - .build()); + .build()); + if (session.getMultiplexed() && isReadWriteTransaction(transactionId)) { + resultSetBuilder.setPrecommitToken(getResultSetPrecommitToken(transactionId)); + } + responseObserver.onNext(resultSetBuilder.build()); } break; default: @@ -1064,7 +1086,8 @@ private void returnResultSet( ResultSet resultSet, ByteString transactionId, TransactionSelector transactionSelector, - StreamObserver responseObserver) { + StreamObserver responseObserver, + Session session) { ResultSetMetadata metadata = resultSet.getMetadata(); if (transactionId != null) { metadata = @@ -1079,7 +1102,12 @@ private void returnResultSet( Transaction transaction = getTemporaryTransactionOrNull(transactionSelector); metadata = metadata.toBuilder().setTransaction(transaction).build(); } - resultSet = resultSet.toBuilder().setMetadata(metadata).build(); + ResultSet.Builder resultSetBuilder = resultSet.toBuilder(); + resultSetBuilder.setMetadata(metadata); + if (session.getMultiplexed() && isReadWriteTransaction(transactionId)) { + resultSetBuilder.setPrecommitToken(getResultSetPrecommitToken(transactionId)); + } + resultSet = resultSetBuilder.build(); responseObserver.onNext(resultSet); } @@ -1174,6 +1202,9 @@ public void executeBatchDml( .build()); } builder.setStatus(status); + if (session.getMultiplexed() && isReadWriteTransaction(transactionId)) { + builder.setPrecommitToken(getExecuteBatchDmlResponsePrecommitToken(transactionId)); + } responseObserver.onNext(builder.build()); responseObserver.onCompleted(); } catch (StatusRuntimeException e) { @@ -1242,7 +1273,8 @@ public void executeStreamingSql( transactionId, request.getTransaction(), responseObserver, - getExecuteStreamingSqlExecutionTime()); + getExecuteStreamingSqlExecutionTime(), + session.getMultiplexed()); break; case UPDATE_COUNT: if (isPartitioned) { @@ -1612,7 +1644,7 @@ public void read(final ReadRequest request, StreamObserver responseOb cols); StatementResult res = getResult(statement); returnResultSet( - res.getResultSet(), transactionId, request.getTransaction(), responseObserver); + res.getResultSet(), transactionId, request.getTransaction(), responseObserver, session); responseObserver.onCompleted(); } catch (StatusRuntimeException e) { responseObserver.onError(e); @@ -1670,7 +1702,8 @@ public void streamingRead( transactionId, request.getTransaction(), responseObserver, - getStreamingReadExecutionTime()); + getStreamingReadExecutionTime(), + session.getMultiplexed()); } catch (StatusRuntimeException e) { responseObserver.onError(e); } catch (Throwable t) { @@ -1683,7 +1716,8 @@ private void returnPartialResultSet( ByteString transactionId, TransactionSelector transactionSelector, StreamObserver responseObserver, - SimulatedExecutionTime executionTime) + SimulatedExecutionTime executionTime, + boolean isMultiplexedSession) throws Exception { ResultSetMetadata metadata = resultSet.getMetadata(); if (transactionId == null) { @@ -1700,7 +1734,11 @@ private void returnPartialResultSet( .build(); } resultSet = resultSet.toBuilder().setMetadata(metadata).build(); - PartialResultSetsIterator iterator = new PartialResultSetsIterator(resultSet); + PartialResultSetsIterator iterator = + new PartialResultSetsIterator( + resultSet, + isMultiplexedSession && isReadWriteTransaction(transactionId), + transactionId); long index = 0L; while (iterator.hasNext()) { SimulatedExecutionTime.checkStreamException( @@ -2034,6 +2072,7 @@ private void commitTransaction(ByteString transactionId) { transactions.remove(transactionId); isPartitionedDmlTransaction.remove(transactionId); transactionLastUsed.remove(transactionId); + transactionSequenceNo.remove(transactionId); } @Override @@ -2065,6 +2104,7 @@ void rollbackTransaction(ByteString transactionId) { transactions.remove(transactionId); isPartitionedDmlTransaction.remove(transactionId); transactionLastUsed.remove(transactionId); + transactionSequenceNo.remove(transactionId); } void markAbortedTransaction(ByteString transactionId) { @@ -2072,6 +2112,7 @@ void markAbortedTransaction(ByteString transactionId) { transactions.remove(transactionId); isPartitionedDmlTransaction.remove(transactionId); transactionLastUsed.remove(transactionId); + transactionSequenceNo.remove(transactionId); } @Override @@ -2276,6 +2317,7 @@ public void reset() { transactionCounters = new ConcurrentHashMap<>(); partitionTokens = new ConcurrentHashMap<>(); transactionLastUsed = new ConcurrentHashMap<>(); + transactionSequenceNo = new ConcurrentHashMap<>(); numSessionsCreated.set(0); stickyGlobalExceptions = false; @@ -2447,4 +2489,30 @@ Session getSession(String name) { } return null; } + + static MultiplexedSessionPrecommitToken getResultSetPrecommitToken(ByteString transactionId) { + return getPrecommitToken("ResultSetPrecommitToken", transactionId); + } + + static MultiplexedSessionPrecommitToken getPartialResultSetPrecommitToken( + ByteString transactionId) { + return getPrecommitToken("PartialResultSetPrecommitToken", transactionId); + } + + static MultiplexedSessionPrecommitToken getExecuteBatchDmlResponsePrecommitToken( + ByteString transactionId) { + return getPrecommitToken("ExecuteBatchDmlResponsePrecommitToken", transactionId); + } + + static MultiplexedSessionPrecommitToken getPrecommitToken( + String value, ByteString transactionId) { + transactionSequenceNo.putIfAbsent(transactionId, new AtomicInteger(0)); + + // Generates an incrementing sequence number + int seqNum = transactionSequenceNo.get(transactionId).incrementAndGet(); + return MultiplexedSessionPrecommitToken.newBuilder() + .setPrecommitToken(ByteString.copyFromUtf8(value)) + .setSeqNum(seqNum) + .build(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java index b6dff424079..c7d7b697d64 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClientMockServerTest.java @@ -16,6 +16,9 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.MockSpannerTestUtil.INVALID_UPDATE_STATEMENT; +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.assertFalse; @@ -24,15 +27,24 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.cloud.NoCredentials; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.AsyncTransactionManager.AsyncTransactionStep; +import com.google.cloud.spanner.AsyncTransactionManager.CommitTimestampFuture; +import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture; import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; import com.google.cloud.spanner.Options.RpcPriority; +import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl; import com.google.cloud.spanner.connection.RandomResultSetGenerator; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; +import com.google.spanner.v1.BeginTransactionRequest; import com.google.spanner.v1.CommitRequest; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.RequestOptions.Priority; @@ -43,6 +55,10 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.junit.Before; import org.junit.BeforeClass; @@ -58,6 +74,11 @@ public class MultiplexedSessionDatabaseClientMockServerTest extends AbstractMock public static void setupResults() { mockSpanner.putStatementResults( StatementResult.query(STATEMENT, new RandomResultSetGenerator(1).generate())); + mockSpanner.putStatementResult(StatementResult.update(UPDATE_STATEMENT, UPDATE_COUNT)); + mockSpanner.putStatementResult( + StatementResult.exception( + INVALID_UPDATE_STATEMENT, + Status.INVALID_ARGUMENT.withDescription("invalid statement").asRuntimeException())); } @Before @@ -71,6 +92,7 @@ public void createSpannerInstance() { SessionPoolOptions.newBuilder() .setUseMultiplexedSession(true) .setUseMultiplexedSessionBlindWrite(true) + .setUseMultiplexedSessionForRW(true) // Set the maintainer to loop once every 1ms .setMultiplexedSessionMaintenanceLoopFrequency(Duration.ofMillis(1L)) // Set multiplexed sessions to be replaced once every 1ms @@ -467,6 +489,600 @@ public void testWriteAtLeastOnceWithExcludeTxnFromChangeStreams() { assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); } + @Test + public void testReadWriteTransactionUsingTransactionRunner() { + // Queries executed within a R/W transaction via TransactionRunner should use a multiplexed + // session. + // During a retry (due to an ABORTED error), the transaction should use the same multiplexed + // session as before, assuming the maintainer hasn't run in the meantime. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared + // after the first call, so the retry should succeed. + mockSpanner.setCommitExecutionTime( + SimulatedExecutionTime.ofException( + mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")))); + + client + .readWriteTransaction() + .run( + transaction -> { + try (ResultSet resultSet = transaction.executeQuery(STATEMENT)) { + //noinspection StatementWithEmptyBody + while (resultSet.next()) { + // ignore + } + } + return null; + }); + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(2, executeSqlRequests.size()); + assertEquals(executeSqlRequests.get(0).getSession(), executeSqlRequests.get(1).getSession()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testReadWriteTransactionUsingTransactionManager() { + // Queries executed within a R/W transaction via TransactionManager should use a multiplexed + // session. + // During a retry (due to an ABORTED error), the transaction should use the same multiplexed + // session as before, assuming the maintainer hasn't run in the meantime. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared + // after the first call, so the retry should succeed. + mockSpanner.setCommitExecutionTime( + SimulatedExecutionTime.ofException( + mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")))); + + try (TransactionManager manager = client.transactionManager()) { + TransactionContext transaction = manager.begin(); + while (true) { + try { + try (ResultSet resultSet = transaction.executeQuery(STATEMENT)) { + //noinspection StatementWithEmptyBody + while (resultSet.next()) { + // ignore + } + } + manager.commit(); + assertNotNull(manager.getCommitTimestamp()); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); + } + } + } + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(2, executeSqlRequests.size()); + assertEquals(executeSqlRequests.get(0).getSession(), executeSqlRequests.get(1).getSession()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testMutationUsingWrite() { + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared + // after the first call, so the retry should succeed. + mockSpanner.setCommitExecutionTime( + SimulatedExecutionTime.ofException( + mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")))); + Timestamp timestamp = + client.write( + Collections.singletonList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build())); + assertNotNull(timestamp); + + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(2, commitRequests.size()); + for (CommitRequest request : commitRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testMutationUsingWriteWithOptions() { + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + CommitResponse response = + client.writeWithOptions( + Collections.singletonList( + Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build()), + Options.tag("app=spanner,env=test")); + assertNotNull(response); + assertNotNull(response.getCommitTimestamp()); + + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(1L, commitRequests.size()); + CommitRequest commit = commitRequests.get(0); + assertNotNull(commit.getRequestOptions()); + assertEquals("app=spanner,env=test", commit.getRequestOptions().getTransactionTag()); + assertTrue(mockSpanner.getSession(commit.getSession()).getMultiplexed()); + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testReadWriteTransactionUsingAsyncTransactionManager() throws Exception { + // Updates executed within a R/W transaction via AsyncTransactionManager should use a + // multiplexed session. + // During a retry (due to an ABORTED error), the transaction should use the same multiplexed + // session as before, assuming the maintainer hasn't run in the meantime. + final AtomicInteger attempt = new AtomicInteger(); + CountDownLatch abortedLatch = new CountDownLatch(1); + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + try (AsyncTransactionManager manager = client.transactionManagerAsync()) { + TransactionContextFuture transactionContextFuture = manager.beginAsync(); + while (true) { + try { + attempt.incrementAndGet(); + AsyncTransactionStep updateCount = + transactionContextFuture.then( + (transaction, ignored) -> transaction.executeUpdateAsync(UPDATE_STATEMENT), + MoreExecutors.directExecutor()); + updateCount.then( + (transaction, ignored) -> { + if (attempt.get() == 1) { + mockSpanner.abortTransaction(transaction); + abortedLatch.countDown(); + } + return ApiFutures.immediateFuture(null); + }, + MoreExecutors.directExecutor()); + abortedLatch.await(10L, TimeUnit.SECONDS); + CommitTimestampFuture commitTimestamp = updateCount.commitAsync(); + assertEquals(UPDATE_COUNT, updateCount.get().longValue()); + assertNotNull(commitTimestamp.get()); + assertEquals(2L, attempt.get()); + break; + } catch (AbortedException e) { + transactionContextFuture = manager.resetForRetryAsync(); + } + } + } + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(2, executeSqlRequests.size()); + assertEquals(executeSqlRequests.get(0).getSession(), executeSqlRequests.get(1).getSession()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testReadWriteTransactionUsingAsyncRunner() throws Exception { + // Updates executed within a R/W transaction via AsyncRunner should use a multiplexed + // session. + // During a retry (due to an ABORTED error), the transaction should use the same multiplexed + // session as before, assuming the maintainer hasn't run in the meantime. + final AtomicInteger attempt = new AtomicInteger(); + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + AsyncRunner runner = client.runAsync(); + ApiFuture updateCount = + runner.runAsync( + txn -> { + ApiFuture updateCount1 = txn.executeUpdateAsync(UPDATE_STATEMENT); + if (attempt.incrementAndGet() == 1) { + mockSpanner.abortTransaction(txn); + } + return updateCount1; + }, + MoreExecutors.directExecutor()); + assertEquals(UPDATE_COUNT, updateCount.get().longValue()); + assertEquals(2L, attempt.get()); + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(2L, executeSqlRequests.size()); + assertEquals(executeSqlRequests.get(0).getSession(), executeSqlRequests.get(1).getSession()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testAsyncRunnerIsNonBlockingWithMultiplexedSession() throws Exception { + mockSpanner.freeze(); + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + AsyncRunner runner = client.runAsync(); + ApiFuture res = + runner.runAsync( + txn -> { + txn.executeUpdateAsync(UPDATE_STATEMENT); + return ApiFutures.immediateFuture(null); + }, + MoreExecutors.directExecutor()); + ApiFuture ts = runner.getCommitTimestamp(); + mockSpanner.unfreeze(); + assertThat(res.get()).isNull(); + assertThat(ts.get()).isNotNull(); + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(1L, executeSqlRequests.size()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + assertNotNull(client.multiplexedSessionDatabaseClient); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsAcquired().get()); + assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get()); + } + + @Test + public void testAbortedReadWriteTxnUsesPreviousTxnIdOnRetryWithInlineBegin() { + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared + // after the first call, so the retry should succeed. + mockSpanner.setCommitExecutionTime( + SimulatedExecutionTime.ofException( + mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")))); + TransactionRunner runner = client.readWriteTransaction(); + AtomicReference validTransactionId = new AtomicReference<>(); + runner.run( + transaction -> { + try (ResultSet resultSet = transaction.executeQuery(STATEMENT)) { + while (resultSet.next()) {} + } + + TransactionContextImpl impl = (TransactionContextImpl) transaction; + if (validTransactionId.get() == null) { + // Track the first not-null transactionId. This transaction gets ABORTED during commit + // operation and gets retried. + validTransactionId.set(impl.transactionId); + } + return null; + }); + + List executeSqlRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class); + assertEquals(2, executeSqlRequests.size()); + + // Verify the requests are executed using multiplexed sessions + for (ExecuteSqlRequest request : executeSqlRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + // Verify that the first request uses inline begin, and the previous transaction ID is set to + // ByteString.EMPTY + assertTrue(executeSqlRequests.get(0).hasTransaction()); + assertTrue(executeSqlRequests.get(0).getTransaction().hasBegin()); + assertTrue(executeSqlRequests.get(0).getTransaction().getBegin().hasReadWrite()); + assertNotNull( + executeSqlRequests + .get(0) + .getTransaction() + .getBegin() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertEquals( + ByteString.EMPTY, + executeSqlRequests + .get(0) + .getTransaction() + .getBegin() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + + // Verify that the second request uses inline begin, and the previous transaction ID is set + // appropriately + assertTrue(executeSqlRequests.get(1).hasTransaction()); + assertTrue(executeSqlRequests.get(1).getTransaction().hasBegin()); + assertTrue(executeSqlRequests.get(1).getTransaction().getBegin().hasReadWrite()); + assertNotNull( + executeSqlRequests + .get(1) + .getTransaction() + .getBegin() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertNotEquals( + ByteString.EMPTY, + executeSqlRequests + .get(1) + .getTransaction() + .getBegin() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertEquals( + validTransactionId.get(), + executeSqlRequests + .get(1) + .getTransaction() + .getBegin() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + } + + @Test + public void testAbortedReadWriteTxnUsesPreviousTxnIdOnRetryWithExplicitBegin() { + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared + // after the first call, so the retry should succeed. + mockSpanner.setCommitExecutionTime( + SimulatedExecutionTime.ofException( + mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")))); + TransactionRunner runner = client.readWriteTransaction(); + AtomicReference validTransactionId = new AtomicReference<>(); + Long updateCount = + runner.run( + transaction -> { + // This update statement carries the BeginTransaction, but fails. This will + // cause the entire transaction to be retried with an explicit + // BeginTransaction RPC to ensure all statements in the transaction are + // actually executed against the same transaction. + TransactionContextImpl impl = (TransactionContextImpl) transaction; + if (validTransactionId.get() == null) { + // Track the first not-null transactionId. This transaction gets ABORTED during + // commit operation and gets retried. + validTransactionId.set(impl.transactionId); + } + SpannerException e = + assertThrows( + SpannerException.class, + () -> transaction.executeUpdate(INVALID_UPDATE_STATEMENT)); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + return transaction.executeUpdate(UPDATE_STATEMENT); + }); + + assertThat(updateCount).isEqualTo(1L); + List beginTransactionRequests = + mockSpanner.getRequestsOfType(BeginTransactionRequest.class); + assertEquals(2, beginTransactionRequests.size()); + + // Verify the requests are executed using multiplexed sessions + for (BeginTransactionRequest request : beginTransactionRequests) { + assertTrue(mockSpanner.getSession(request.getSession()).getMultiplexed()); + } + + // Verify that explicit begin transaction is called during retry, and the previous transaction + // ID is set to ByteString.EMPTY + assertTrue(beginTransactionRequests.get(0).hasOptions()); + assertTrue(beginTransactionRequests.get(0).getOptions().hasReadWrite()); + assertNotNull( + beginTransactionRequests + .get(0) + .getOptions() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertEquals( + ByteString.EMPTY, + beginTransactionRequests + .get(0) + .getOptions() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + + // The previous transaction with id (txn1) fails during commit operation with ABORTED error. + // Verify that explicit begin transaction is called during retry, and the previous transaction + // ID is not ByteString.EMPTY (should be set to txn1) + assertTrue(beginTransactionRequests.get(1).hasOptions()); + assertTrue(beginTransactionRequests.get(1).getOptions().hasReadWrite()); + assertNotNull( + beginTransactionRequests + .get(1) + .getOptions() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertNotEquals( + ByteString.EMPTY, + beginTransactionRequests + .get(1) + .getOptions() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + assertEquals( + validTransactionId.get(), + beginTransactionRequests + .get(1) + .getOptions() + .getReadWrite() + .getMultiplexedSessionPreviousTransactionId()); + } + + @Test + public void testPrecommitTokenForResultSet() { + // This test verifies that the precommit token received from the ResultSet is properly tracked + // and set in the CommitRequest. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + + Long count = + client + .readWriteTransaction() + .run( + transaction -> { + long res = transaction.executeUpdate(UPDATE_STATEMENT); + + // Verify that the latest precommit token is tracked in the transaction context. + TransactionContextImpl impl = (TransactionContextImpl) transaction; + assertNotNull(impl.getLatestPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ResultSetPrecommitToken"), + impl.getLatestPrecommitToken().getPrecommitToken()); + return res; + }); + + assertNotNull(count); + assertEquals(1, count.longValue()); + + // Verify that the latest precommit token is set in the CommitRequest + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(1, commitRequests.size()); + assertTrue(mockSpanner.getSession(commitRequests.get(0).getSession()).getMultiplexed()); + assertNotNull(commitRequests.get(0).getPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ResultSetPrecommitToken"), + commitRequests.get(0).getPrecommitToken().getPrecommitToken()); + } + + @Test + public void testPrecommitTokenForExecuteBatchDmlResponse() { + // This test verifies that the precommit token received from the ExecuteBatchDmlResponse is + // properly tracked and set in the CommitRequest. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + + long[] count = + client + .readWriteTransaction() + .run( + transaction -> { + long[] res = transaction.batchUpdate(Lists.newArrayList(UPDATE_STATEMENT)); + + // Verify that the latest precommit token is tracked in the transaction context. + TransactionContextImpl impl = (TransactionContextImpl) transaction; + assertNotNull(impl.getLatestPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ExecuteBatchDmlResponsePrecommitToken"), + impl.getLatestPrecommitToken().getPrecommitToken()); + return res; + }); + + assertNotNull(count); + assertEquals(1, count.length); + + // Verify that the latest precommit token is set in the CommitRequest + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(1, commitRequests.size()); + assertTrue(mockSpanner.getSession(commitRequests.get(0).getSession()).getMultiplexed()); + assertNotNull(commitRequests.get(0).getPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ExecuteBatchDmlResponsePrecommitToken"), + commitRequests.get(0).getPrecommitToken().getPrecommitToken()); + } + + @Test + public void testPrecommitTokenForPartialResultSet() { + // This test verifies that the precommit token received from the PartialResultSet is properly + // tracked and set in the CommitRequest. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + + client + .readWriteTransaction() + .run( + transaction -> { + ResultSet resultSet = transaction.executeQuery(STATEMENT); + //noinspection StatementWithEmptyBody + while (resultSet.next()) { + // ignore + } + + // Verify that the latest precommit token is tracked in the transaction context. + TransactionContextImpl impl = (TransactionContextImpl) transaction; + assertNotNull(impl.getLatestPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("PartialResultSetPrecommitToken"), + impl.getLatestPrecommitToken().getPrecommitToken()); + return null; + }); + + // Verify that the latest precommit token is set in the CommitRequest + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(1, commitRequests.size()); + assertTrue(mockSpanner.getSession(commitRequests.get(0).getSession()).getMultiplexed()); + assertNotNull(commitRequests.get(0).getPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("PartialResultSetPrecommitToken"), + commitRequests.get(0).getPrecommitToken().getPrecommitToken()); + } + + @Test + public void testTxnTracksPrecommitTokenWithLatestSeqNo() { + // This test ensures that the read-write transaction tracks the precommit token with the + // highest sequence number and sets it in the CommitRequest. + DatabaseClientImpl client = + (DatabaseClientImpl) spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + + client + .readWriteTransaction() + .run( + transaction -> { + // Returns a ResultSet containing the precommit token (ResultSetPrecommitToken) + transaction.executeUpdate(UPDATE_STATEMENT); + + // Returns a PartialResultSet containing the precommit token + // (PartialResultSetPrecommitToken) + ResultSet resultSet = transaction.executeQuery(STATEMENT); + //noinspection StatementWithEmptyBody + while (resultSet.next()) { + // ignore + } + + // Returns an ExecuteBatchDmlResponse containing the precommit token + // (ExecuteBatchDmlResponsePrecommitToken). + // Since this is the last request received by the mock Spanner, it should be the most + // recent precommit token tracked by the transaction context. + transaction.batchUpdate(Lists.newArrayList(UPDATE_STATEMENT)); + + // Verify that the latest precommit token with highest sequence number is tracked in + // the transaction context. + TransactionContextImpl impl = (TransactionContextImpl) transaction; + assertNotNull(impl.getLatestPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ExecuteBatchDmlResponsePrecommitToken"), + impl.getLatestPrecommitToken().getPrecommitToken()); + return null; + }); + + // Verify that the latest precommit token is set in the CommitRequest + List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class); + assertEquals(1, commitRequests.size()); + assertTrue(mockSpanner.getSession(commitRequests.get(0).getSession()).getMultiplexed()); + assertNotNull(commitRequests.get(0).getPrecommitToken()); + assertEquals( + ByteString.copyFromUtf8("ExecuteBatchDmlResponsePrecommitToken"), + commitRequests.get(0).getPrecommitToken().getPrecommitToken()); + } + private void waitForSessionToBeReplaced(DatabaseClientImpl client) { assertNotNull(client.multiplexedSessionDatabaseClient); SessionReference sessionReference = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java index d9586acc956..8e3d0986343 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java @@ -94,6 +94,7 @@ public static void setup() { Attributes.builder() .put(BuiltInMetricsConstant.PROJECT_ID_KEY, "test-project") .put(BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY, "unknown") + .put(BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY, "false") .put( BuiltInMetricsConstant.LOCATION_ID_KEY, BuiltInOpenTelemetryMetricsProvider.detectClientLocation()) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java index c973b7e471e..2a399e6f486 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java @@ -24,6 +24,7 @@ import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.common.io.Resources; import com.google.protobuf.util.JsonFormat; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.Transaction; import java.math.BigDecimal; @@ -56,6 +57,9 @@ public SpannerException onError(SpannerException e, boolean withBeginTransaction @Override public void onDone(boolean withBeginTransaction) {} + + @Override + public void onPrecommitToken(MultiplexedSessionPrecommitToken token) {} } public ReadFormatTestRunner(Class clazz) throws InitializationError { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsHelper.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsHelper.java index fc494c6f3ff..404973336ba 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsHelper.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsHelper.java @@ -19,6 +19,7 @@ import com.google.cloud.spanner.AbstractResultSet.CloseableIterator; import com.google.cloud.spanner.AbstractResultSet.Listener; import com.google.protobuf.ListValue; +import com.google.spanner.v1.MultiplexedSessionPrecommitToken; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.Transaction; import java.util.Iterator; @@ -82,6 +83,9 @@ public SpannerException onError(SpannerException e, boolean withBeginTransaction @Override public void onDone(boolean withBeginTransaction) {} + + @Override + public void onPrecommitToken(MultiplexedSessionPrecommitToken token) {} }); } } 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 998678e4296..c3e8d887ded 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 @@ -1495,9 +1495,10 @@ public void testSessionNotFoundReadWriteTransaction() { .build(); when(closedSession.asyncClose()) .thenReturn(ApiFutures.immediateFuture(Empty.getDefaultInstance())); - when(closedSession.newTransaction(Options.fromTransactionOptions())) + when(closedSession.newTransaction(eq(Options.fromTransactionOptions()), any())) .thenReturn(closedTransactionContext); - when(closedSession.beginTransactionAsync(any(), eq(true), any())).thenThrow(sessionNotFound); + when(closedSession.beginTransactionAsync(any(), eq(true), any(), any())) + .thenThrow(sessionNotFound); when(closedSession.getTracer()).thenReturn(tracer); TransactionRunnerImpl closedTransactionRunner = new TransactionRunnerImpl(closedSession); closedTransactionRunner.setSpan(span); @@ -1510,9 +1511,9 @@ public void testSessionNotFoundReadWriteTransaction() { when(openSession.getName()) .thenReturn("projects/dummy/instances/dummy/database/dummy/sessions/session-open"); final TransactionContextImpl openTransactionContext = mock(TransactionContextImpl.class); - when(openSession.newTransaction(Options.fromTransactionOptions())) + when(openSession.newTransaction(eq(Options.fromTransactionOptions()), any())) .thenReturn(openTransactionContext); - when(openSession.beginTransactionAsync(any(), eq(true), any())) + when(openSession.beginTransactionAsync(any(), eq(true), any(), any())) .thenReturn(ApiFutures.immediateFuture(ByteString.copyFromUtf8("open-txn"))); when(openSession.getTracer()).thenReturn(tracer); TransactionRunnerImpl openTransactionRunner = new TransactionRunnerImpl(openSession); 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 c3fcf1c7480..10b13125152 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 @@ -20,7 +20,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -98,7 +100,7 @@ public void setUp() { @Test public void beginCalledTwiceFails() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); assertThat(manager.begin()).isEqualTo(txn); assertThat(manager.getState()).isEqualTo(TransactionState.STARTED); IllegalStateException e = assertThrows(IllegalStateException.class, () -> manager.begin()); @@ -126,7 +128,7 @@ public void resetBeforeBeginFails() { @Test public void transactionRolledBackOnClose() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); when(txn.isAborted()).thenReturn(false); manager.begin(); manager.close(); @@ -135,7 +137,7 @@ public void transactionRolledBackOnClose() { @Test public void commitSucceeds() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); Timestamp commitTimestamp = Timestamp.ofTimeMicroseconds(1); CommitResponse response = new CommitResponse(commitTimestamp); when(txn.getCommitResponse()).thenReturn(response); @@ -147,7 +149,7 @@ public void commitSucceeds() { @Test public void resetAfterSuccessfulCommitFails() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); manager.begin(); manager.commit(); IllegalStateException e = @@ -157,21 +159,21 @@ public void resetAfterSuccessfulCommitFails() { @Test public void resetAfterAbortSucceeds() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); manager.begin(); doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "")).when(txn).commit(); assertThrows(AbortedException.class, () -> manager.commit()); assertEquals(TransactionState.ABORTED, manager.getState()); txn = Mockito.mock(TransactionRunnerImpl.TransactionContextImpl.class); - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); assertThat(manager.resetForRetry()).isEqualTo(txn); assertThat(manager.getState()).isEqualTo(TransactionState.STARTED); } @Test public void resetAfterErrorFails() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); manager.begin(); doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.UNKNOWN, "")).when(txn).commit(); SpannerException e = assertThrows(SpannerException.class, () -> manager.commit()); @@ -184,7 +186,7 @@ public void resetAfterErrorFails() { @Test public void rollbackAfterCommitFails() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); manager.begin(); manager.commit(); IllegalStateException e = assertThrows(IllegalStateException.class, () -> manager.rollback()); @@ -193,7 +195,7 @@ public void rollbackAfterCommitFails() { @Test public void commitAfterRollbackFails() { - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); manager.begin(); manager.rollback(); IllegalStateException e = assertThrows(IllegalStateException.class, () -> manager.commit()); @@ -363,4 +365,61 @@ public void inlineBegin() { assertThat(transactionsStarted.get()).isEqualTo(1); } } + + // This test ensures that when a transaction is aborted in a multiplexed session, + // the transaction ID of the aborted transaction is saved during the retry when a new transaction + // is created. + @Test + public void storePreviousTxnIdOnAbortForMultiplexedSession() { + txn = Mockito.mock(TransactionRunnerImpl.TransactionContextImpl.class); + final ByteString mockTransactionId = ByteString.copyFromUtf8("mockTransactionId"); + txn.transactionId = mockTransactionId; + when(session.newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY)) + .thenReturn(txn); + manager.begin(); + // Verify that for the first transaction attempt, the `previousTransactionId` is + // ByteString.EMPTY. + // This is because no transaction has been previously aborted at this point. + verify(session).newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY); + doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "")).when(txn).commit(); + assertThrows(AbortedException.class, () -> manager.commit()); + + txn = Mockito.mock(TransactionRunnerImpl.TransactionContextImpl.class); + when(txn.getPreviousTransactionId()).thenReturn(mockTransactionId); + when(session.newTransaction(Options.fromTransactionOptions(), mockTransactionId)) + .thenReturn(txn); + when(session.getIsMultiplexed()).thenReturn(true); + assertThat(manager.resetForRetry()).isEqualTo(txn); + // Verify that in the first retry attempt, the `previousTransactionId` is passed to the new + // transaction. + // This allows Spanner to retry the transaction using the ID of the aborted transaction. + verify(session).newTransaction(Options.fromTransactionOptions(), mockTransactionId); + } + + // This test ensures that when a transaction is aborted in a regular session, + // the transaction ID of the aborted transaction is not saved during the retry when a new + // transaction is created. + @Test + public void skipTxnIdStorageOnAbortForRegularSession() { + txn = Mockito.mock(TransactionRunnerImpl.TransactionContextImpl.class); + final ByteString mockTransactionId = ByteString.copyFromUtf8("mockTransactionId"); + txn.transactionId = mockTransactionId; + when(session.newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY)) + .thenReturn(txn); + manager.begin(); + verify(session).newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY); + doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "")).when(txn).commit(); + assertThrows(AbortedException.class, () -> manager.commit()); + clearInvocations(session); + + txn = Mockito.mock(TransactionRunnerImpl.TransactionContextImpl.class); + when(session.newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY)) + .thenReturn(txn); + when(session.getIsMultiplexed()).thenReturn(false); + assertThat(manager.resetForRetry()).isEqualTo(txn); + // Verify that in the first retry attempt, the `previousTransactionId` is not passed to the new + // transaction + // in case of regular sessions. + verify(session).newTransaction(Options.fromTransactionOptions(), ByteString.EMPTY); + } } 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 c647bb3642a..1fd6817ea96 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 @@ -20,6 +20,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; @@ -117,7 +118,7 @@ public void setUp() { tracer = new TraceWrapper(Tracing.getTracer(), OpenTelemetry.noop().getTracer(""), false); firstRun = true; when(session.getErrorHandler()).thenReturn(DefaultErrorHandler.INSTANCE); - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(txn); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())).thenReturn(txn); when(session.getTracer()).thenReturn(tracer); when(rpc.executeQuery(Mockito.any(ExecuteSqlRequest.class), Mockito.anyMap(), eq(true))) .thenAnswer( @@ -343,7 +344,8 @@ private long[] batchDmlException(int status) { .setTracer(session.getTracer()) .setSpan(session.getTracer().getCurrentSpan()) .build(); - when(session.newTransaction(Options.fromTransactionOptions())).thenReturn(transaction); + when(session.newTransaction(eq(Options.fromTransactionOptions()), any())) + .thenReturn(transaction); when(session.getName()).thenReturn(SessionId.of("p", "i", "d", "test").getName()); TransactionRunnerImpl runner = new TransactionRunnerImpl(session); runner.setSpan(span); 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 94a44579acf..f826ec08dfc 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 @@ -322,6 +322,27 @@ public void testBuildWithRouteToLeader() { assertTrue(options.isRouteToLeader()); } + @Test + public void testBuildWithEndToEndTracingEnabled() { + final String BASE_URI = + "cloudspanner:/projects/test-project-123/instances/test-instance-123/databases/test-database-123"; + ConnectionOptions.Builder builder = ConnectionOptions.newBuilder(); + builder.setUri(BASE_URI + "?enableEndToEndTracing=true"); + builder.setCredentialsUrl(FILE_TEST_PATH); + ConnectionOptions options = builder.build(); + assertEquals(options.getHost(), DEFAULT_HOST); + assertEquals(options.getProjectId(), TEST_PROJECT); + assertEquals(options.getInstanceId(), TEST_INSTANCE); + assertEquals(options.getDatabaseName(), TEST_DATABASE); + assertTrue(options.isEndToEndTracingEnabled()); + + // Test for default behavior for enableEndToEndTracing property. + builder = ConnectionOptions.newBuilder().setUri(BASE_URI); + builder.setCredentialsUrl(FILE_TEST_PATH); + options = builder.build(); + assertFalse(options.isEndToEndTracingEnabled()); + } + @Test public void testBuildWithAutoConfigEmulatorAndHost() { ConnectionOptions.Builder builder = ConnectionOptions.newBuilder(); 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 19d49139635..fea0b8aa6cf 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 @@ -563,6 +563,58 @@ public void testEnableApiTracing() { .build())); } + @Test + public void testEnableEndToEndTracing() { + SpannerPoolKey keyWithoutApiTracingConfig = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/p/instances/i/databases/d") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithApiTracingEnabled = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableEndToEndTracing=true") + .setCredentials(NoCredentials.getInstance()) + .build()); + SpannerPoolKey keyWithApiTracingDisabled = + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableEndToEndTracing=false") + .setCredentials(NoCredentials.getInstance()) + .build()); + + assertNotEquals(keyWithoutApiTracingConfig, keyWithApiTracingEnabled); + assertEquals(keyWithoutApiTracingConfig, keyWithApiTracingDisabled); + assertNotEquals(keyWithApiTracingEnabled, keyWithApiTracingDisabled); + + assertEquals( + keyWithApiTracingEnabled, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableEndToEndTracing=true") + .setCredentials(NoCredentials.getInstance()) + .build())); + assertEquals( + keyWithApiTracingDisabled, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/p/instances/i/databases/d?enableEndToEndTracing=false") + .setCredentials(NoCredentials.getInstance()) + .build())); + assertEquals( + keyWithoutApiTracingConfig, + SpannerPoolKey.of( + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/p/instances/i/databases/d") + .setCredentials(NoCredentials.getInstance()) + .build())); + } + @Test public void testOpenTelemetry() { SpannerPool pool = createSubjectAndMocks(); diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index b4513a2a6e2..e771e71abb2 100644 --- a/grpc-google-cloud-spanner-admin-database-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 grpc-google-cloud-spanner-admin-database-v1 GRPC library for grpc-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index a2e709c84c9..51fe501cee2 100644 --- a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 grpc-google-cloud-spanner-admin-instance-v1 GRPC library for grpc-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/grpc-google-cloud-spanner-executor-v1/pom.xml b/grpc-google-cloud-spanner-executor-v1/pom.xml index 2a4d5fef094..e27a468ce1f 100644 --- a/grpc-google-cloud-spanner-executor-v1/pom.xml +++ b/grpc-google-cloud-spanner-executor-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-executor-v1 - 6.79.0 + 6.80.0 grpc-google-cloud-spanner-executor-v1 GRPC library for google-cloud-spanner com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 5cb30f447cc..b2a38960dc9 100644 --- a/grpc-google-cloud-spanner-v1/pom.xml +++ b/grpc-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/pom.xml b/pom.xml index 7c79683002b..45fa0cc683b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.79.0 + 6.80.0 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.37.0 + 3.38.0 @@ -61,47 +61,47 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-executor-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-executor-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 com.google.cloud google-cloud-spanner - 6.79.0 + 6.80.0 @@ -171,7 +171,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.7.0 + 3.8.0 diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index 6d888586df1..744f7b4cf44 100644 --- a/proto-google-cloud-spanner-admin-database-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.79.0 + 6.80.0 proto-google-cloud-spanner-admin-database-v1 PROTO library for proto-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 0a234f357e8..da6d55f4779 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.79.0 + 6.80.0 proto-google-cloud-spanner-admin-instance-v1 PROTO library for proto-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/Instance.java b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/Instance.java index 1caf9592536..5ed73ef4173 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/Instance.java +++ b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/Instance.java @@ -46,6 +46,7 @@ private Instance() { state_ = 0; endpointUris_ = com.google.protobuf.LazyStringArrayList.emptyList(); edition_ = 0; + defaultBackupScheduleType_ = 0; } @java.lang.Override @@ -424,6 +425,177 @@ private Edition(int value) { // @@protoc_insertion_point(enum_scope:google.spanner.admin.instance.v1.Instance.Edition) } + /** + * + * + *
+   * Indicates the default backup behavior for new databases within the
+   * instance.
+   * 
+ * + * Protobuf enum {@code google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType} + */ + public enum DefaultBackupScheduleType implements com.google.protobuf.ProtocolMessageEnum { + /** + * + * + *
+     * Not specified.
+     * 
+ * + * DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED = 0; + */ + DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED(0), + /** + * + * + *
+     * No default backup schedule will be created automatically on creation of a
+     * database within the instance.
+     * 
+ * + * NONE = 1; + */ + NONE(1), + /** + * + * + *
+     * A default backup schedule will be created automatically on creation of a
+     * database within the instance. The default backup schedule creates a full
+     * backup every 24 hours and retains the backup for a period of 7 days. Once
+     * created, the default backup schedule can be edited/deleted similar to any
+     * other backup schedule.
+     * 
+ * + * AUTOMATIC = 2; + */ + AUTOMATIC(2), + UNRECOGNIZED(-1), + ; + + /** + * + * + *
+     * Not specified.
+     * 
+ * + * DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED = 0; + */ + public static final int DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED_VALUE = 0; + /** + * + * + *
+     * No default backup schedule will be created automatically on creation of a
+     * database within the instance.
+     * 
+ * + * NONE = 1; + */ + public static final int NONE_VALUE = 1; + /** + * + * + *
+     * A default backup schedule will be created automatically on creation of a
+     * database within the instance. The default backup schedule creates a full
+     * backup every 24 hours and retains the backup for a period of 7 days. Once
+     * created, the default backup schedule can be edited/deleted similar to any
+     * other backup schedule.
+     * 
+ * + * AUTOMATIC = 2; + */ + public static final int AUTOMATIC_VALUE = 2; + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static DefaultBackupScheduleType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static DefaultBackupScheduleType forNumber(int value) { + switch (value) { + case 0: + return DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED; + case 1: + return NONE; + case 2: + return AUTOMATIC; + default: + return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + + private static final com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public DefaultBackupScheduleType findValueByNumber(int number) { + return DefaultBackupScheduleType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + + public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { + return getDescriptor(); + } + + public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { + return com.google.spanner.admin.instance.v1.Instance.getDescriptor().getEnumTypes().get(2); + } + + private static final DefaultBackupScheduleType[] VALUES = values(); + + public static DefaultBackupScheduleType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private DefaultBackupScheduleType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType) + } + private int bitField0_; public static final int NAME_FIELD_NUMBER = 1; @@ -1266,6 +1438,65 @@ public com.google.spanner.admin.instance.v1.Instance.Edition getEdition() { : result; } + public static final int DEFAULT_BACKUP_SCHEDULE_TYPE_FIELD_NUMBER = 23; + private int defaultBackupScheduleType_ = 0; + /** + * + * + *
+   * Optional. Controls the default backup behavior for new databases within the
+   * instance.
+   *
+   * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+   * backup schedules are not allowed for free instances.
+   *
+   * In the `GetInstance` or `ListInstances` response, if the value of
+   * default_backup_schedule_type is unset or NONE, no default backup
+   * schedule will be created for new databases within the instance.
+   * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The enum numeric value on the wire for defaultBackupScheduleType. + */ + @java.lang.Override + public int getDefaultBackupScheduleTypeValue() { + return defaultBackupScheduleType_; + } + /** + * + * + *
+   * Optional. Controls the default backup behavior for new databases within the
+   * instance.
+   *
+   * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+   * backup schedules are not allowed for free instances.
+   *
+   * In the `GetInstance` or `ListInstances` response, if the value of
+   * default_backup_schedule_type is unset or NONE, no default backup
+   * schedule will be created for new databases within the instance.
+   * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The defaultBackupScheduleType. + */ + @java.lang.Override + public com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType + getDefaultBackupScheduleType() { + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType result = + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType.forNumber( + defaultBackupScheduleType_); + return result == null + ? com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType.UNRECOGNIZED + : result; + } + private byte memoizedIsInitialized = -1; @java.lang.Override @@ -1320,6 +1551,12 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io != com.google.spanner.admin.instance.v1.Instance.Edition.EDITION_UNSPECIFIED.getNumber()) { output.writeEnum(20, edition_); } + if (defaultBackupScheduleType_ + != com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType + .DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED + .getNumber()) { + output.writeEnum(23, defaultBackupScheduleType_); + } getUnknownFields().writeTo(output); } @@ -1384,6 +1621,12 @@ public int getSerializedSize() { != com.google.spanner.admin.instance.v1.Instance.Edition.EDITION_UNSPECIFIED.getNumber()) { size += com.google.protobuf.CodedOutputStream.computeEnumSize(20, edition_); } + if (defaultBackupScheduleType_ + != com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType + .DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED + .getNumber()) { + size += com.google.protobuf.CodedOutputStream.computeEnumSize(23, defaultBackupScheduleType_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -1423,6 +1666,7 @@ public boolean equals(final java.lang.Object obj) { if (!getUpdateTime().equals(other.getUpdateTime())) return false; } if (edition_ != other.edition_) return false; + if (defaultBackupScheduleType_ != other.defaultBackupScheduleType_) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -1472,6 +1716,8 @@ public int hashCode() { } hash = (37 * hash) + EDITION_FIELD_NUMBER; hash = (53 * hash) + edition_; + hash = (37 * hash) + DEFAULT_BACKUP_SCHEDULE_TYPE_FIELD_NUMBER; + hash = (53 * hash) + defaultBackupScheduleType_; hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -1676,6 +1922,7 @@ public Builder clear() { updateTimeBuilder_ = null; } edition_ = 0; + defaultBackupScheduleType_ = 0; return this; } @@ -1770,6 +2017,9 @@ private void buildPartial0(com.google.spanner.admin.instance.v1.Instance result) if (((from_bitField0_ & 0x00001000) != 0)) { result.edition_ = edition_; } + if (((from_bitField0_ & 0x00002000) != 0)) { + result.defaultBackupScheduleType_ = defaultBackupScheduleType_; + } result.bitField0_ |= to_bitField0_; } @@ -1893,6 +2143,9 @@ public Builder mergeFrom(com.google.spanner.admin.instance.v1.Instance other) { if (other.edition_ != 0) { setEditionValue(other.getEditionValue()); } + if (other.defaultBackupScheduleType_ != 0) { + setDefaultBackupScheduleTypeValue(other.getDefaultBackupScheduleTypeValue()); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -2013,6 +2266,12 @@ public Builder mergeFrom( bitField0_ |= 0x00001000; break; } // case 160 + case 184: + { + defaultBackupScheduleType_ = input.readEnum(); + bitField0_ |= 0x00002000; + break; + } // case 184 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { @@ -4401,6 +4660,151 @@ public Builder clearEdition() { return this; } + private int defaultBackupScheduleType_ = 0; + /** + * + * + *
+     * Optional. Controls the default backup behavior for new databases within the
+     * instance.
+     *
+     * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+     * backup schedules are not allowed for free instances.
+     *
+     * In the `GetInstance` or `ListInstances` response, if the value of
+     * default_backup_schedule_type is unset or NONE, no default backup
+     * schedule will be created for new databases within the instance.
+     * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The enum numeric value on the wire for defaultBackupScheduleType. + */ + @java.lang.Override + public int getDefaultBackupScheduleTypeValue() { + return defaultBackupScheduleType_; + } + /** + * + * + *
+     * Optional. Controls the default backup behavior for new databases within the
+     * instance.
+     *
+     * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+     * backup schedules are not allowed for free instances.
+     *
+     * In the `GetInstance` or `ListInstances` response, if the value of
+     * default_backup_schedule_type is unset or NONE, no default backup
+     * schedule will be created for new databases within the instance.
+     * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @param value The enum numeric value on the wire for defaultBackupScheduleType to set. + * @return This builder for chaining. + */ + public Builder setDefaultBackupScheduleTypeValue(int value) { + defaultBackupScheduleType_ = value; + bitField0_ |= 0x00002000; + onChanged(); + return this; + } + /** + * + * + *
+     * Optional. Controls the default backup behavior for new databases within the
+     * instance.
+     *
+     * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+     * backup schedules are not allowed for free instances.
+     *
+     * In the `GetInstance` or `ListInstances` response, if the value of
+     * default_backup_schedule_type is unset or NONE, no default backup
+     * schedule will be created for new databases within the instance.
+     * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The defaultBackupScheduleType. + */ + @java.lang.Override + public com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType + getDefaultBackupScheduleType() { + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType result = + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType.forNumber( + defaultBackupScheduleType_); + return result == null + ? com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType.UNRECOGNIZED + : result; + } + /** + * + * + *
+     * Optional. Controls the default backup behavior for new databases within the
+     * instance.
+     *
+     * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+     * backup schedules are not allowed for free instances.
+     *
+     * In the `GetInstance` or `ListInstances` response, if the value of
+     * default_backup_schedule_type is unset or NONE, no default backup
+     * schedule will be created for new databases within the instance.
+     * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @param value The defaultBackupScheduleType to set. + * @return This builder for chaining. + */ + public Builder setDefaultBackupScheduleType( + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00002000; + defaultBackupScheduleType_ = value.getNumber(); + onChanged(); + return this; + } + /** + * + * + *
+     * Optional. Controls the default backup behavior for new databases within the
+     * instance.
+     *
+     * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+     * backup schedules are not allowed for free instances.
+     *
+     * In the `GetInstance` or `ListInstances` response, if the value of
+     * default_backup_schedule_type is unset or NONE, no default backup
+     * schedule will be created for new databases within the instance.
+     * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return This builder for chaining. + */ + public Builder clearDefaultBackupScheduleType() { + bitField0_ = (bitField0_ & ~0x00002000); + defaultBackupScheduleType_ = 0; + 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-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/InstanceOrBuilder.java b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/InstanceOrBuilder.java index db7d23f94e0..6ebc4e67d45 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/InstanceOrBuilder.java +++ b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/InstanceOrBuilder.java @@ -655,4 +655,50 @@ java.lang.String getLabelsOrDefault( * @return The edition. */ com.google.spanner.admin.instance.v1.Instance.Edition getEdition(); + + /** + * + * + *
+   * Optional. Controls the default backup behavior for new databases within the
+   * instance.
+   *
+   * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+   * backup schedules are not allowed for free instances.
+   *
+   * In the `GetInstance` or `ListInstances` response, if the value of
+   * default_backup_schedule_type is unset or NONE, no default backup
+   * schedule will be created for new databases within the instance.
+   * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The enum numeric value on the wire for defaultBackupScheduleType. + */ + int getDefaultBackupScheduleTypeValue(); + /** + * + * + *
+   * Optional. Controls the default backup behavior for new databases within the
+   * instance.
+   *
+   * Note that `AUTOMATIC` is not permitted for free instances, as backups and
+   * backup schedules are not allowed for free instances.
+   *
+   * In the `GetInstance` or `ListInstances` response, if the value of
+   * default_backup_schedule_type is unset or NONE, no default backup
+   * schedule will be created for new databases within the instance.
+   * 
+ * + * + * .google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType default_backup_schedule_type = 23 [(.google.api.field_behavior) = OPTIONAL]; + * + * + * @return The defaultBackupScheduleType. + */ + com.google.spanner.admin.instance.v1.Instance.DefaultBackupScheduleType + getDefaultBackupScheduleType(); } diff --git a/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/SpannerInstanceAdminProto.java b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/SpannerInstanceAdminProto.java index 2edfb759d12..b286514fd32 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/SpannerInstanceAdminProto.java +++ b/proto-google-cloud-spanner-admin-instance-v1/src/main/java/com/google/spanner/admin/instance/v1/SpannerInstanceAdminProto.java @@ -278,7 +278,7 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + ".admin.instance.v1.AutoscalingConfig.Aut" + "oscalingLimitsB\003\340A\001\022E\n8autoscaling_targe" + "t_high_priority_cpu_utilization_percent\030" - + "\002 \001(\005B\003\340A\001\"\305\007\n\010Instance\022\021\n\004name\030\001 \001(\tB\003\340" + + "\002 \001(\005B\003\340A\001\"\232\t\n\010Instance\022\021\n\004name\030\001 \001(\tB\003\340" + "A\002\022=\n\006config\030\002 \001(\tB-\340A\002\372A\'\n%spanner.goog" + "leapis.com/InstanceConfig\022\031\n\014display_nam" + "e\030\003 \001(\tB\003\340A\002\022\022\n\nnode_count\030\005 \001(\005\022\030\n\020proc" @@ -295,299 +295,305 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + "tobuf.TimestampB\003\340A\003\0224\n\013update_time\030\014 \001(" + "\0132\032.google.protobuf.TimestampB\003\340A\003\022H\n\007ed" + "ition\030\024 \001(\01622.google.spanner.admin.insta" - + "nce.v1.Instance.EditionB\003\340A\001\032-\n\013LabelsEn" - + "try\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"7\n\005S" - + "tate\022\025\n\021STATE_UNSPECIFIED\020\000\022\014\n\010CREATING\020" - + "\001\022\t\n\005READY\020\002\"U\n\007Edition\022\027\n\023EDITION_UNSPE" - + "CIFIED\020\000\022\014\n\010STANDARD\020\001\022\016\n\nENTERPRISE\020\002\022\023" - + "\n\017ENTERPRISE_PLUS\020\003:M\352AJ\n\037spanner.google" - + "apis.com/Instance\022\'projects/{project}/in" - + "stances/{instance}\"\210\001\n\032ListInstanceConfi" - + "gsRequest\022C\n\006parent\030\001 \001(\tB3\340A\002\372A-\n+cloud" - + "resourcemanager.googleapis.com/Project\022\021" - + "\n\tpage_size\030\002 \001(\005\022\022\n\npage_token\030\003 \001(\t\"\202\001" - + "\n\033ListInstanceConfigsResponse\022J\n\020instanc" - + "e_configs\030\001 \003(\01320.google.spanner.admin.i" - + "nstance.v1.InstanceConfig\022\027\n\017next_page_t" - + "oken\030\002 \001(\t\"W\n\030GetInstanceConfigRequest\022;" - + "\n\004name\030\001 \001(\tB-\340A\002\372A\'\n%spanner.googleapis" - + ".com/InstanceConfig\"\352\001\n\033CreateInstanceCo" - + "nfigRequest\022C\n\006parent\030\001 \001(\tB3\340A\002\372A-\n+clo" - + "udresourcemanager.googleapis.com/Project" - + "\022\037\n\022instance_config_id\030\002 \001(\tB\003\340A\002\022N\n\017ins" - + "tance_config\030\003 \001(\01320.google.spanner.admi" - + "n.instance.v1.InstanceConfigB\003\340A\002\022\025\n\rval" - + "idate_only\030\004 \001(\010\"\272\001\n\033UpdateInstanceConfi" - + "gRequest\022N\n\017instance_config\030\001 \001(\01320.goog" - + "le.spanner.admin.instance.v1.InstanceCon" - + "figB\003\340A\002\0224\n\013update_mask\030\002 \001(\0132\032.google.p" - + "rotobuf.FieldMaskB\003\340A\002\022\025\n\rvalidate_only\030" - + "\003 \001(\010\"\177\n\033DeleteInstanceConfigRequest\022;\n\004" - + "name\030\001 \001(\tB-\340A\002\372A\'\n%spanner.googleapis.c" - + "om/InstanceConfig\022\014\n\004etag\030\002 \001(\t\022\025\n\rvalid" - + "ate_only\030\003 \001(\010\"\241\001\n#ListInstanceConfigOpe" - + "rationsRequest\022C\n\006parent\030\001 \001(\tB3\340A\002\372A-\n+" - + "cloudresourcemanager.googleapis.com/Proj" - + "ect\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\"r\n$ListInstanceConfig" - + "OperationsResponse\0221\n\noperations\030\001 \003(\0132\035" - + ".google.longrunning.Operation\022\027\n\017next_pa" - + "ge_token\030\002 \001(\t\"{\n\022GetInstanceRequest\0225\n\004" - + "name\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapis.c" - + "om/Instance\022.\n\nfield_mask\030\002 \001(\0132\032.google" - + ".protobuf.FieldMask\"\271\001\n\025CreateInstanceRe" + + "nce.v1.Instance.EditionB\003\340A\001\022o\n\034default_" + + "backup_schedule_type\030\027 \001(\0162D.google.span" + + "ner.admin.instance.v1.Instance.DefaultBa" + + "ckupScheduleTypeB\003\340A\001\032-\n\013LabelsEntry\022\013\n\003" + + "key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"7\n\005State\022\025\n" + + "\021STATE_UNSPECIFIED\020\000\022\014\n\010CREATING\020\001\022\t\n\005RE" + + "ADY\020\002\"U\n\007Edition\022\027\n\023EDITION_UNSPECIFIED\020" + + "\000\022\014\n\010STANDARD\020\001\022\016\n\nENTERPRISE\020\002\022\023\n\017ENTER" + + "PRISE_PLUS\020\003\"b\n\031DefaultBackupScheduleTyp" + + "e\022,\n(DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPEC" + + "IFIED\020\000\022\010\n\004NONE\020\001\022\r\n\tAUTOMATIC\020\002:M\352AJ\n\037s" + + "panner.googleapis.com/Instance\022\'projects" + + "/{project}/instances/{instance}\"\210\001\n\032List" + + "InstanceConfigsRequest\022C\n\006parent\030\001 \001(\tB3" + + "\340A\002\372A-\n+cloudresourcemanager.googleapis." + + "com/Project\022\021\n\tpage_size\030\002 \001(\005\022\022\n\npage_t" + + "oken\030\003 \001(\t\"\202\001\n\033ListInstanceConfigsRespon" + + "se\022J\n\020instance_configs\030\001 \003(\01320.google.sp" + + "anner.admin.instance.v1.InstanceConfig\022\027" + + "\n\017next_page_token\030\002 \001(\t\"W\n\030GetInstanceCo" + + "nfigRequest\022;\n\004name\030\001 \001(\tB-\340A\002\372A\'\n%spann" + + "er.googleapis.com/InstanceConfig\"\352\001\n\033Cre" + + "ateInstanceConfigRequest\022C\n\006parent\030\001 \001(\t" + + "B3\340A\002\372A-\n+cloudresourcemanager.googleapi" + + "s.com/Project\022\037\n\022instance_config_id\030\002 \001(" + + "\tB\003\340A\002\022N\n\017instance_config\030\003 \001(\01320.google" + + ".spanner.admin.instance.v1.InstanceConfi" + + "gB\003\340A\002\022\025\n\rvalidate_only\030\004 \001(\010\"\272\001\n\033Update" + + "InstanceConfigRequest\022N\n\017instance_config" + + "\030\001 \001(\01320.google.spanner.admin.instance.v" + + "1.InstanceConfigB\003\340A\002\0224\n\013update_mask\030\002 \001" + + "(\0132\032.google.protobuf.FieldMaskB\003\340A\002\022\025\n\rv" + + "alidate_only\030\003 \001(\010\"\177\n\033DeleteInstanceConf" + + "igRequest\022;\n\004name\030\001 \001(\tB-\340A\002\372A\'\n%spanner" + + ".googleapis.com/InstanceConfig\022\014\n\004etag\030\002" + + " \001(\t\022\025\n\rvalidate_only\030\003 \001(\010\"\241\001\n#ListInst" + + "anceConfigOperationsRequest\022C\n\006parent\030\001 " + + "\001(\tB3\340A\002\372A-\n+cloudresourcemanager.google" + + "apis.com/Project\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\"r\n$ListI" + + "nstanceConfigOperationsResponse\0221\n\nopera" + + "tions\030\001 \003(\0132\035.google.longrunning.Operati" + + "on\022\027\n\017next_page_token\030\002 \001(\t\"{\n\022GetInstan" + + "ceRequest\0225\n\004name\030\001 \001(\tB\'\340A\002\372A!\n\037spanner" + + ".googleapis.com/Instance\022.\n\nfield_mask\030\002" + + " \001(\0132\032.google.protobuf.FieldMask\"\271\001\n\025Cre" + + "ateInstanceRequest\022C\n\006parent\030\001 \001(\tB3\340A\002\372" + + "A-\n+cloudresourcemanager.googleapis.com/" + + "Project\022\030\n\013instance_id\030\002 \001(\tB\003\340A\002\022A\n\010ins" + + "tance\030\003 \001(\0132*.google.spanner.admin.insta" + + "nce.v1.InstanceB\003\340A\002\"\311\001\n\024ListInstancesRe" + "quest\022C\n\006parent\030\001 \001(\tB3\340A\002\372A-\n+cloudreso" - + "urcemanager.googleapis.com/Project\022\030\n\013in" - + "stance_id\030\002 \001(\tB\003\340A\002\022A\n\010instance\030\003 \001(\0132*" + + "urcemanager.googleapis.com/Project\022\021\n\tpa" + + "ge_size\030\002 \001(\005\022\022\n\npage_token\030\003 \001(\t\022\016\n\006fil" + + "ter\030\004 \001(\t\0225\n\021instance_deadline\030\005 \001(\0132\032.g" + + "oogle.protobuf.Timestamp\"\204\001\n\025ListInstanc" + + "esResponse\022=\n\tinstances\030\001 \003(\0132*.google.s" + + "panner.admin.instance.v1.Instance\022\027\n\017nex" + + "t_page_token\030\002 \001(\t\022\023\n\013unreachable\030\003 \003(\t\"" + + "\217\001\n\025UpdateInstanceRequest\022A\n\010instance\030\001 " + + "\001(\0132*.google.spanner.admin.instance.v1.I" + + "nstanceB\003\340A\002\0223\n\nfield_mask\030\002 \001(\0132\032.googl" + + "e.protobuf.FieldMaskB\003\340A\002\"N\n\025DeleteInsta" + + "nceRequest\0225\n\004name\030\001 \001(\tB\'\340A\002\372A!\n\037spanne" + + "r.googleapis.com/Instance\"\277\002\n\026CreateInst" + + "anceMetadata\022<\n\010instance\030\001 \001(\0132*.google." + + "spanner.admin.instance.v1.Instance\022.\n\nst" + + "art_time\030\002 \001(\0132\032.google.protobuf.Timesta" + + "mp\022/\n\013cancel_time\030\003 \001(\0132\032.google.protobu" + + "f.Timestamp\022,\n\010end_time\030\004 \001(\0132\032.google.p" + + "rotobuf.Timestamp\022X\n\033expected_fulfillmen" + + "t_period\030\005 \001(\01623.google.spanner.admin.in" + + "stance.v1.FulfillmentPeriod\"\277\002\n\026UpdateIn" + + "stanceMetadata\022<\n\010instance\030\001 \001(\0132*.googl" + + "e.spanner.admin.instance.v1.Instance\022.\n\n" + + "start_time\030\002 \001(\0132\032.google.protobuf.Times" + + "tamp\022/\n\013cancel_time\030\003 \001(\0132\032.google.proto" + + "buf.Timestamp\022,\n\010end_time\030\004 \001(\0132\032.google" + + ".protobuf.Timestamp\022X\n\033expected_fulfillm" + + "ent_period\030\005 \001(\01623.google.spanner.admin." + + "instance.v1.FulfillmentPeriod\"\341\001\n\034Create" + + "InstanceConfigMetadata\022I\n\017instance_confi" + + "g\030\001 \001(\01320.google.spanner.admin.instance." + + "v1.InstanceConfig\022E\n\010progress\030\002 \001(\01323.go" + + "ogle.spanner.admin.instance.v1.Operation" + + "Progress\022/\n\013cancel_time\030\003 \001(\0132\032.google.p" + + "rotobuf.Timestamp\"\341\001\n\034UpdateInstanceConf" + + "igMetadata\022I\n\017instance_config\030\001 \001(\01320.go" + + "ogle.spanner.admin.instance.v1.InstanceC" + + "onfig\022E\n\010progress\030\002 \001(\01323.google.spanner" + + ".admin.instance.v1.OperationProgress\022/\n\013" + + "cancel_time\030\003 \001(\0132\032.google.protobuf.Time" + + "stamp\"\216\005\n\021InstancePartition\022\021\n\004name\030\001 \001(" + + "\tB\003\340A\002\022=\n\006config\030\002 \001(\tB-\340A\002\372A\'\n%spanner." + + "googleapis.com/InstanceConfig\022\031\n\014display" + + "_name\030\003 \001(\tB\003\340A\002\022\024\n\nnode_count\030\005 \001(\005H\000\022\032" + + "\n\020processing_units\030\006 \001(\005H\000\022M\n\005state\030\007 \001(" + + "\01629.google.spanner.admin.instance.v1.Ins" + + "tancePartition.StateB\003\340A\003\0224\n\013create_time" + + "\030\010 \001(\0132\032.google.protobuf.TimestampB\003\340A\003\022" + + "4\n\013update_time\030\t \001(\0132\032.google.protobuf.T" + + "imestampB\003\340A\003\022\"\n\025referencing_databases\030\n" + + " \003(\tB\003\340A\003\022 \n\023referencing_backups\030\013 \003(\tB\003" + + "\340A\003\022\014\n\004etag\030\014 \001(\t\"7\n\005State\022\025\n\021STATE_UNSP" + + "ECIFIED\020\000\022\014\n\010CREATING\020\001\022\t\n\005READY\020\002:~\352A{\n" + + "(spanner.googleapis.com/InstancePartitio" + + "n\022Oprojects/{project}/instances/{instanc" + + "e}/instancePartitions/{instance_partitio" + + "n}B\022\n\020compute_capacity\"\201\002\n\037CreateInstanc" + + "ePartitionMetadata\022O\n\022instance_partition" + + "\030\001 \001(\01323.google.spanner.admin.instance.v" + + "1.InstancePartition\022.\n\nstart_time\030\002 \001(\0132" + + "\032.google.protobuf.Timestamp\022/\n\013cancel_ti" + + "me\030\003 \001(\0132\032.google.protobuf.Timestamp\022,\n\010" + + "end_time\030\004 \001(\0132\032.google.protobuf.Timesta" + + "mp\"\323\001\n\036CreateInstancePartitionRequest\0227\n" + + "\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapi" + + "s.com/Instance\022\"\n\025instance_partition_id\030" + + "\002 \001(\tB\003\340A\002\022T\n\022instance_partition\030\003 \001(\01323" + ".google.spanner.admin.instance.v1.Instan" - + "ceB\003\340A\002\"\311\001\n\024ListInstancesRequest\022C\n\006pare" - + "nt\030\001 \001(\tB3\340A\002\372A-\n+cloudresourcemanager.g" - + "oogleapis.com/Project\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\0225\n\021" - + "instance_deadline\030\005 \001(\0132\032.google.protobu" - + "f.Timestamp\"\204\001\n\025ListInstancesResponse\022=\n" - + "\tinstances\030\001 \003(\0132*.google.spanner.admin." - + "instance.v1.Instance\022\027\n\017next_page_token\030" - + "\002 \001(\t\022\023\n\013unreachable\030\003 \003(\t\"\217\001\n\025UpdateIns" - + "tanceRequest\022A\n\010instance\030\001 \001(\0132*.google." - + "spanner.admin.instance.v1.InstanceB\003\340A\002\022" - + "3\n\nfield_mask\030\002 \001(\0132\032.google.protobuf.Fi" - + "eldMaskB\003\340A\002\"N\n\025DeleteInstanceRequest\0225\n" - + "\004name\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapis." - + "com/Instance\"\277\002\n\026CreateInstanceMetadata\022" - + "<\n\010instance\030\001 \001(\0132*.google.spanner.admin" - + ".instance.v1.Instance\022.\n\nstart_time\030\002 \001(" - + "\0132\032.google.protobuf.Timestamp\022/\n\013cancel_" - + "time\030\003 \001(\0132\032.google.protobuf.Timestamp\022," - + "\n\010end_time\030\004 \001(\0132\032.google.protobuf.Times" - + "tamp\022X\n\033expected_fulfillment_period\030\005 \001(" - + "\01623.google.spanner.admin.instance.v1.Ful" - + "fillmentPeriod\"\277\002\n\026UpdateInstanceMetadat" - + "a\022<\n\010instance\030\001 \001(\0132*.google.spanner.adm" - + "in.instance.v1.Instance\022.\n\nstart_time\030\002 " - + "\001(\0132\032.google.protobuf.Timestamp\022/\n\013cance" - + "l_time\030\003 \001(\0132\032.google.protobuf.Timestamp" - + "\022,\n\010end_time\030\004 \001(\0132\032.google.protobuf.Tim" - + "estamp\022X\n\033expected_fulfillment_period\030\005 " - + "\001(\01623.google.spanner.admin.instance.v1.F" - + "ulfillmentPeriod\"\341\001\n\034CreateInstanceConfi" - + "gMetadata\022I\n\017instance_config\030\001 \001(\01320.goo" - + "gle.spanner.admin.instance.v1.InstanceCo" - + "nfig\022E\n\010progress\030\002 \001(\01323.google.spanner." - + "admin.instance.v1.OperationProgress\022/\n\013c" - + "ancel_time\030\003 \001(\0132\032.google.protobuf.Times" - + "tamp\"\341\001\n\034UpdateInstanceConfigMetadata\022I\n" - + "\017instance_config\030\001 \001(\01320.google.spanner." - + "admin.instance.v1.InstanceConfig\022E\n\010prog" - + "ress\030\002 \001(\01323.google.spanner.admin.instan" - + "ce.v1.OperationProgress\022/\n\013cancel_time\030\003" - + " \001(\0132\032.google.protobuf.Timestamp\"\216\005\n\021Ins" - + "tancePartition\022\021\n\004name\030\001 \001(\tB\003\340A\002\022=\n\006con" - + "fig\030\002 \001(\tB-\340A\002\372A\'\n%spanner.googleapis.co" - + "m/InstanceConfig\022\031\n\014display_name\030\003 \001(\tB\003" - + "\340A\002\022\024\n\nnode_count\030\005 \001(\005H\000\022\032\n\020processing_" - + "units\030\006 \001(\005H\000\022M\n\005state\030\007 \001(\01629.google.sp" - + "anner.admin.instance.v1.InstancePartitio" - + "n.StateB\003\340A\003\0224\n\013create_time\030\010 \001(\0132\032.goog" - + "le.protobuf.TimestampB\003\340A\003\0224\n\013update_tim" - + "e\030\t \001(\0132\032.google.protobuf.TimestampB\003\340A\003" - + "\022\"\n\025referencing_databases\030\n \003(\tB\003\340A\003\022 \n\023" - + "referencing_backups\030\013 \003(\tB\003\340A\003\022\014\n\004etag\030\014" - + " \001(\t\"7\n\005State\022\025\n\021STATE_UNSPECIFIED\020\000\022\014\n\010" - + "CREATING\020\001\022\t\n\005READY\020\002:~\352A{\n(spanner.goog" - + "leapis.com/InstancePartition\022Oprojects/{" - + "project}/instances/{instance}/instancePa" - + "rtitions/{instance_partition}B\022\n\020compute" - + "_capacity\"\201\002\n\037CreateInstancePartitionMet" - + "adata\022O\n\022instance_partition\030\001 \001(\01323.goog" - + "le.spanner.admin.instance.v1.InstancePar" - + "tition\022.\n\nstart_time\030\002 \001(\0132\032.google.prot" - + "obuf.Timestamp\022/\n\013cancel_time\030\003 \001(\0132\032.go" - + "ogle.protobuf.Timestamp\022,\n\010end_time\030\004 \001(" - + "\0132\032.google.protobuf.Timestamp\"\323\001\n\036Create" - + "InstancePartitionRequest\0227\n\006parent\030\001 \001(\t" - + "B\'\340A\002\372A!\n\037spanner.googleapis.com/Instanc" - + "e\022\"\n\025instance_partition_id\030\002 \001(\tB\003\340A\002\022T\n" - + "\022instance_partition\030\003 \001(\01323.google.spann" - + "er.admin.instance.v1.InstancePartitionB\003" - + "\340A\002\"n\n\036DeleteInstancePartitionRequest\022>\n" - + "\004name\030\001 \001(\tB0\340A\002\372A*\n(spanner.googleapis." - + "com/InstancePartition\022\014\n\004etag\030\002 \001(\t\"]\n\033G" - + "etInstancePartitionRequest\022>\n\004name\030\001 \001(\t" - + "B0\340A\002\372A*\n(spanner.googleapis.com/Instanc" - + "ePartition\"\253\001\n\036UpdateInstancePartitionRe" - + "quest\022T\n\022instance_partition\030\001 \001(\01323.goog" - + "le.spanner.admin.instance.v1.InstancePar" - + "titionB\003\340A\002\0223\n\nfield_mask\030\002 \001(\0132\032.google" - + ".protobuf.FieldMaskB\003\340A\002\"\201\002\n\037UpdateInsta" - + "ncePartitionMetadata\022O\n\022instance_partiti" - + "on\030\001 \001(\01323.google.spanner.admin.instance" - + ".v1.InstancePartition\022.\n\nstart_time\030\002 \001(" - + "\0132\032.google.protobuf.Timestamp\022/\n\013cancel_" - + "time\030\003 \001(\0132\032.google.protobuf.Timestamp\022," - + "\n\010end_time\030\004 \001(\0132\032.google.protobuf.Times" - + "tamp\"\305\001\n\035ListInstancePartitionsRequest\0227" - + "\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleap" - + "is.com/Instance\022\021\n\tpage_size\030\002 \001(\005\022\022\n\npa" - + "ge_token\030\003 \001(\t\022D\n\033instance_partition_dea" - + "dline\030\004 \001(\0132\032.google.protobuf.TimestampB" - + "\003\340A\001\"\240\001\n\036ListInstancePartitionsResponse\022" - + "P\n\023instance_partitions\030\001 \003(\01323.google.sp" - + "anner.admin.instance.v1.InstancePartitio" - + "n\022\027\n\017next_page_token\030\002 \001(\t\022\023\n\013unreachabl" - + "e\030\003 \003(\t\"\355\001\n&ListInstancePartitionOperati" - + "onsRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037span" - + "ner.googleapis.com/Instance\022\023\n\006filter\030\002 " - + "\001(\tB\003\340A\001\022\026\n\tpage_size\030\003 \001(\005B\003\340A\001\022\027\n\npage" - + "_token\030\004 \001(\tB\003\340A\001\022D\n\033instance_partition_" - + "deadline\030\005 \001(\0132\032.google.protobuf.Timesta" - + "mpB\003\340A\001\"\236\001\n\'ListInstancePartitionOperati" - + "onsResponse\0221\n\noperations\030\001 \003(\0132\035.google" - + ".longrunning.Operation\022\027\n\017next_page_toke" - + "n\030\002 \001(\t\022\'\n\037unreachable_instance_partitio" - + "ns\030\003 \003(\t\"\222\001\n\023MoveInstanceRequest\0225\n\004name" - + "\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.googleapis.com/I" - + "nstance\022D\n\rtarget_config\030\002 \001(\tB-\340A\002\372A\'\n%" - + "spanner.googleapis.com/InstanceConfig\"\026\n" - + "\024MoveInstanceResponse\"\245\001\n\024MoveInstanceMe" - + "tadata\022\025\n\rtarget_config\030\001 \001(\t\022E\n\010progres" - + "s\030\002 \001(\01323.google.spanner.admin.instance." - + "v1.OperationProgress\022/\n\013cancel_time\030\003 \001(" - + "\0132\032.google.protobuf.Timestamp2\332\'\n\rInstan" - + "ceAdmin\022\314\001\n\023ListInstanceConfigs\022<.google" - + ".spanner.admin.instance.v1.ListInstanceC" - + "onfigsRequest\032=.google.spanner.admin.ins" - + "tance.v1.ListInstanceConfigsResponse\"8\332A" - + "\006parent\202\323\344\223\002)\022\'/v1/{parent=projects/*}/i" - + "nstanceConfigs\022\271\001\n\021GetInstanceConfig\022:.g" - + "oogle.spanner.admin.instance.v1.GetInsta" - + "nceConfigRequest\0320.google.spanner.admin." - + "instance.v1.InstanceConfig\"6\332A\004name\202\323\344\223\002" - + ")\022\'/v1/{name=projects/*/instanceConfigs/" - + "*}\022\310\002\n\024CreateInstanceConfig\022=.google.spa" - + "nner.admin.instance.v1.CreateInstanceCon" - + "figRequest\032\035.google.longrunning.Operatio" - + "n\"\321\001\312Ap\n/google.spanner.admin.instance.v" - + "1.InstanceConfig\022=google.spanner.admin.i" - + "nstance.v1.CreateInstanceConfigMetadata\332" - + "A)parent,instance_config,instance_config" - + "_id\202\323\344\223\002,\"\'/v1/{parent=projects/*}/insta" - + "nceConfigs:\001*\022\312\002\n\024UpdateInstanceConfig\022=" - + ".google.spanner.admin.instance.v1.Update" - + "InstanceConfigRequest\032\035.google.longrunni" - + "ng.Operation\"\323\001\312Ap\n/google.spanner.admin" - + ".instance.v1.InstanceConfig\022=google.span" - + "ner.admin.instance.v1.UpdateInstanceConf" - + "igMetadata\332A\033instance_config,update_mask" - + "\202\323\344\223\002<27/v1/{instance_config.name=projec" - + "ts/*/instanceConfigs/*}:\001*\022\245\001\n\024DeleteIns" + + "cePartitionB\003\340A\002\"n\n\036DeleteInstancePartit" + + "ionRequest\022>\n\004name\030\001 \001(\tB0\340A\002\372A*\n(spanne" + + "r.googleapis.com/InstancePartition\022\014\n\004et" + + "ag\030\002 \001(\t\"]\n\033GetInstancePartitionRequest\022" + + ">\n\004name\030\001 \001(\tB0\340A\002\372A*\n(spanner.googleapi" + + "s.com/InstancePartition\"\253\001\n\036UpdateInstan" + + "cePartitionRequest\022T\n\022instance_partition" + + "\030\001 \001(\01323.google.spanner.admin.instance.v" + + "1.InstancePartitionB\003\340A\002\0223\n\nfield_mask\030\002" + + " \001(\0132\032.google.protobuf.FieldMaskB\003\340A\002\"\201\002" + + "\n\037UpdateInstancePartitionMetadata\022O\n\022ins" + + "tance_partition\030\001 \001(\01323.google.spanner.a" + + "dmin.instance.v1.InstancePartition\022.\n\nst" + + "art_time\030\002 \001(\0132\032.google.protobuf.Timesta" + + "mp\022/\n\013cancel_time\030\003 \001(\0132\032.google.protobu" + + "f.Timestamp\022,\n\010end_time\030\004 \001(\0132\032.google.p" + + "rotobuf.Timestamp\"\305\001\n\035ListInstancePartit" + + "ionsRequest\0227\n\006parent\030\001 \001(\tB\'\340A\002\372A!\n\037spa" + + "nner.googleapis.com/Instance\022\021\n\tpage_siz" + + "e\030\002 \001(\005\022\022\n\npage_token\030\003 \001(\t\022D\n\033instance_" + + "partition_deadline\030\004 \001(\0132\032.google.protob" + + "uf.TimestampB\003\340A\001\"\240\001\n\036ListInstancePartit" + + "ionsResponse\022P\n\023instance_partitions\030\001 \003(" + + "\01323.google.spanner.admin.instance.v1.Ins" + + "tancePartition\022\027\n\017next_page_token\030\002 \001(\t\022" + + "\023\n\013unreachable\030\003 \003(\t\"\355\001\n&ListInstancePar" + + "titionOperationsRequest\0227\n\006parent\030\001 \001(\tB" + + "\'\340A\002\372A!\n\037spanner.googleapis.com/Instance" + + "\022\023\n\006filter\030\002 \001(\tB\003\340A\001\022\026\n\tpage_size\030\003 \001(\005" + + "B\003\340A\001\022\027\n\npage_token\030\004 \001(\tB\003\340A\001\022D\n\033instan" + + "ce_partition_deadline\030\005 \001(\0132\032.google.pro" + + "tobuf.TimestampB\003\340A\001\"\236\001\n\'ListInstancePar" + + "titionOperationsResponse\0221\n\noperations\030\001" + + " \003(\0132\035.google.longrunning.Operation\022\027\n\017n" + + "ext_page_token\030\002 \001(\t\022\'\n\037unreachable_inst" + + "ance_partitions\030\003 \003(\t\"\222\001\n\023MoveInstanceRe" + + "quest\0225\n\004name\030\001 \001(\tB\'\340A\002\372A!\n\037spanner.goo" + + "gleapis.com/Instance\022D\n\rtarget_config\030\002 " + + "\001(\tB-\340A\002\372A\'\n%spanner.googleapis.com/Inst" + + "anceConfig\"\026\n\024MoveInstanceResponse\"\245\001\n\024M" + + "oveInstanceMetadata\022\025\n\rtarget_config\030\001 \001" + + "(\t\022E\n\010progress\030\002 \001(\01323.google.spanner.ad" + + "min.instance.v1.OperationProgress\022/\n\013can" + + "cel_time\030\003 \001(\0132\032.google.protobuf.Timesta" + + "mp2\332\'\n\rInstanceAdmin\022\314\001\n\023ListInstanceCon" + + "figs\022<.google.spanner.admin.instance.v1." + + "ListInstanceConfigsRequest\032=.google.span" + + "ner.admin.instance.v1.ListInstanceConfig" + + "sResponse\"8\332A\006parent\202\323\344\223\002)\022\'/v1/{parent=" + + "projects/*}/instanceConfigs\022\271\001\n\021GetInsta" + + "nceConfig\022:.google.spanner.admin.instanc" + + "e.v1.GetInstanceConfigRequest\0320.google.s" + + "panner.admin.instance.v1.InstanceConfig\"" + + "6\332A\004name\202\323\344\223\002)\022\'/v1/{name=projects/*/ins" + + "tanceConfigs/*}\022\310\002\n\024CreateInstanceConfig" + + "\022=.google.spanner.admin.instance.v1.Crea" + + "teInstanceConfigRequest\032\035.google.longrun" + + "ning.Operation\"\321\001\312Ap\n/google.spanner.adm" + + "in.instance.v1.InstanceConfig\022=google.sp" + + "anner.admin.instance.v1.CreateInstanceCo" + + "nfigMetadata\332A)parent,instance_config,in" + + "stance_config_id\202\323\344\223\002,\"\'/v1/{parent=proj" + + "ects/*}/instanceConfigs:\001*\022\312\002\n\024UpdateIns" + "tanceConfig\022=.google.spanner.admin.insta" - + "nce.v1.DeleteInstanceConfigRequest\032\026.goo" - + "gle.protobuf.Empty\"6\332A\004name\202\323\344\223\002)*\'/v1/{" - + "name=projects/*/instanceConfigs/*}\022\360\001\n\034L" - + "istInstanceConfigOperations\022E.google.spa" - + "nner.admin.instance.v1.ListInstanceConfi" - + "gOperationsRequest\032F.google.spanner.admi" - + "n.instance.v1.ListInstanceConfigOperatio" - + "nsResponse\"A\332A\006parent\202\323\344\223\0022\0220/v1/{parent" - + "=projects/*}/instanceConfigOperations\022\264\001" - + "\n\rListInstances\0226.google.spanner.admin.i" - + "nstance.v1.ListInstancesRequest\0327.google" - + ".spanner.admin.instance.v1.ListInstances" - + "Response\"2\332A\006parent\202\323\344\223\002#\022!/v1/{parent=p" - + "rojects/*}/instances\022\344\001\n\026ListInstancePar" - + "titions\022?.google.spanner.admin.instance." - + "v1.ListInstancePartitionsRequest\032@.googl" - + "e.spanner.admin.instance.v1.ListInstance" - + "PartitionsResponse\"G\332A\006parent\202\323\344\223\0028\0226/v1" - + "/{parent=projects/*/instances/*}/instanc" - + "ePartitions\022\241\001\n\013GetInstance\0224.google.spa" - + "nner.admin.instance.v1.GetInstanceReques" - + "t\032*.google.spanner.admin.instance.v1.Ins" - + "tance\"0\332A\004name\202\323\344\223\002#\022!/v1/{name=projects" - + "/*/instances/*}\022\234\002\n\016CreateInstance\0227.goo" - + "gle.spanner.admin.instance.v1.CreateInst" - + "anceRequest\032\035.google.longrunning.Operati" - + "on\"\261\001\312Ad\n)google.spanner.admin.instance." - + "v1.Instance\0227google.spanner.admin.instan" - + "ce.v1.CreateInstanceMetadata\332A\033parent,in" - + "stance_id,instance\202\323\344\223\002&\"!/v1/{parent=pr" - + "ojects/*}/instances:\001*\022\235\002\n\016UpdateInstanc" - + "e\0227.google.spanner.admin.instance.v1.Upd" - + "ateInstanceRequest\032\035.google.longrunning." - + "Operation\"\262\001\312Ad\n)google.spanner.admin.in" - + "stance.v1.Instance\0227google.spanner.admin" - + ".instance.v1.UpdateInstanceMetadata\332A\023in" - + "stance,field_mask\202\323\344\223\002/2*/v1/{instance.n" - + "ame=projects/*/instances/*}:\001*\022\223\001\n\016Delet" - + "eInstance\0227.google.spanner.admin.instanc" - + "e.v1.DeleteInstanceRequest\032\026.google.prot" - + "obuf.Empty\"0\332A\004name\202\323\344\223\002#*!/v1/{name=pro" - + "jects/*/instances/*}\022\232\001\n\014SetIamPolicy\022\"." - + "google.iam.v1.SetIamPolicyRequest\032\025.goog" - + "le.iam.v1.Policy\"O\332A\017resource,policy\202\323\344\223" - + "\0027\"2/v1/{resource=projects/*/instances/*" - + "}:setIamPolicy:\001*\022\223\001\n\014GetIamPolicy\022\".goo" - + "gle.iam.v1.GetIamPolicyRequest\032\025.google." - + "iam.v1.Policy\"H\332A\010resource\202\323\344\223\0027\"2/v1/{r" - + "esource=projects/*/instances/*}:getIamPo" - + "licy:\001*\022\305\001\n\022TestIamPermissions\022(.google." - + "iam.v1.TestIamPermissionsRequest\032).googl" - + "e.iam.v1.TestIamPermissionsResponse\"Z\332A\024" - + "resource,permissions\202\323\344\223\002=\"8/v1/{resourc" - + "e=projects/*/instances/*}:testIamPermiss" - + "ions:\001*\022\321\001\n\024GetInstancePartition\022=.googl" - + "e.spanner.admin.instance.v1.GetInstanceP" - + "artitionRequest\0323.google.spanner.admin.i" - + "nstance.v1.InstancePartition\"E\332A\004name\202\323\344" - + "\223\0028\0226/v1/{name=projects/*/instances/*/in" - + "stancePartitions/*}\022\351\002\n\027CreateInstancePa" - + "rtition\022@.google.spanner.admin.instance." - + "v1.CreateInstancePartitionRequest\032\035.goog" - + "le.longrunning.Operation\"\354\001\312Av\n2google.s" - + "panner.admin.instance.v1.InstancePartiti" - + "on\022@google.spanner.admin.instance.v1.Cre" - + "ateInstancePartitionMetadata\332A/parent,in" - + "stance_partition,instance_partition_id\202\323" - + "\344\223\002;\"6/v1/{parent=projects/*/instances/*" - + "}/instancePartitions:\001*\022\272\001\n\027DeleteInstan" - + "cePartition\022@.google.spanner.admin.insta" - + "nce.v1.DeleteInstancePartitionRequest\032\026." - + "google.protobuf.Empty\"E\332A\004name\202\323\344\223\0028*6/v" - + "1/{name=projects/*/instances/*/instanceP" - + "artitions/*}\022\352\002\n\027UpdateInstancePartition" - + "\022@.google.spanner.admin.instance.v1.Upda" - + "teInstancePartitionRequest\032\035.google.long" - + "running.Operation\"\355\001\312Av\n2google.spanner." - + "admin.instance.v1.InstancePartition\022@goo" - + "gle.spanner.admin.instance.v1.UpdateInst" - + "ancePartitionMetadata\332A\035instance_partiti" - + "on,field_mask\202\323\344\223\002N2I/v1/{instance_parti" - + "tion.name=projects/*/instances/*/instanc" - + "ePartitions/*}:\001*\022\210\002\n\037ListInstancePartit" - + "ionOperations\022H.google.spanner.admin.ins" - + "tance.v1.ListInstancePartitionOperations" - + "Request\032I.google.spanner.admin.instance." - + "v1.ListInstancePartitionOperationsRespon" - + "se\"P\332A\006parent\202\323\344\223\002A\022?/v1/{parent=project" - + "s/*/instances/*}/instancePartitionOperat" - + "ions\022\211\002\n\014MoveInstance\0225.google.spanner.a" - + "dmin.instance.v1.MoveInstanceRequest\032\035.g" - + "oogle.longrunning.Operation\"\242\001\312An\n5googl" - + "e.spanner.admin.instance.v1.MoveInstance" - + "Response\0225google.spanner.admin.instance." - + "v1.MoveInstanceMetadata\202\323\344\223\002+\"&/v1/{name" - + "=projects/*/instances/*}:move:\001*\032x\312A\026spa" - + "nner.googleapis.com\322A\\https://www.google" - + "apis.com/auth/cloud-platform,https://www" - + ".googleapis.com/auth/spanner.adminB\213\002\n$c" - + "om.google.spanner.admin.instance.v1B\031Spa" - + "nnerInstanceAdminProtoP\001ZFcloud.google.c" - + "om/go/spanner/admin/instance/apiv1/insta" - + "ncepb;instancepb\252\002&Google.Cloud.Spanner." - + "Admin.Instance.V1\312\002&Google\\Cloud\\Spanner" - + "\\Admin\\Instance\\V1\352\002+Google::Cloud::Span" - + "ner::Admin::Instance::V1b\006proto3" + + "nce.v1.UpdateInstanceConfigRequest\032\035.goo" + + "gle.longrunning.Operation\"\323\001\312Ap\n/google." + + "spanner.admin.instance.v1.InstanceConfig" + + "\022=google.spanner.admin.instance.v1.Updat" + + "eInstanceConfigMetadata\332A\033instance_confi" + + "g,update_mask\202\323\344\223\002<27/v1/{instance_confi" + + "g.name=projects/*/instanceConfigs/*}:\001*\022" + + "\245\001\n\024DeleteInstanceConfig\022=.google.spanne" + + "r.admin.instance.v1.DeleteInstanceConfig" + + "Request\032\026.google.protobuf.Empty\"6\332A\004name" + + "\202\323\344\223\002)*\'/v1/{name=projects/*/instanceCon" + + "figs/*}\022\360\001\n\034ListInstanceConfigOperations" + + "\022E.google.spanner.admin.instance.v1.List" + + "InstanceConfigOperationsRequest\032F.google" + + ".spanner.admin.instance.v1.ListInstanceC" + + "onfigOperationsResponse\"A\332A\006parent\202\323\344\223\0022" + + "\0220/v1/{parent=projects/*}/instanceConfig" + + "Operations\022\264\001\n\rListInstances\0226.google.sp" + + "anner.admin.instance.v1.ListInstancesReq" + + "uest\0327.google.spanner.admin.instance.v1." + + "ListInstancesResponse\"2\332A\006parent\202\323\344\223\002#\022!" + + "/v1/{parent=projects/*}/instances\022\344\001\n\026Li" + + "stInstancePartitions\022?.google.spanner.ad" + + "min.instance.v1.ListInstancePartitionsRe" + + "quest\032@.google.spanner.admin.instance.v1" + + ".ListInstancePartitionsResponse\"G\332A\006pare" + + "nt\202\323\344\223\0028\0226/v1/{parent=projects/*/instanc" + + "es/*}/instancePartitions\022\241\001\n\013GetInstance" + + "\0224.google.spanner.admin.instance.v1.GetI" + + "nstanceRequest\032*.google.spanner.admin.in" + + "stance.v1.Instance\"0\332A\004name\202\323\344\223\002#\022!/v1/{" + + "name=projects/*/instances/*}\022\234\002\n\016CreateI" + + "nstance\0227.google.spanner.admin.instance." + + "v1.CreateInstanceRequest\032\035.google.longru" + + "nning.Operation\"\261\001\312Ad\n)google.spanner.ad" + + "min.instance.v1.Instance\0227google.spanner" + + ".admin.instance.v1.CreateInstanceMetadat" + + "a\332A\033parent,instance_id,instance\202\323\344\223\002&\"!/" + + "v1/{parent=projects/*}/instances:\001*\022\235\002\n\016" + + "UpdateInstance\0227.google.spanner.admin.in" + + "stance.v1.UpdateInstanceRequest\032\035.google" + + ".longrunning.Operation\"\262\001\312Ad\n)google.spa" + + "nner.admin.instance.v1.Instance\0227google." + + "spanner.admin.instance.v1.UpdateInstance" + + "Metadata\332A\023instance,field_mask\202\323\344\223\002/2*/v" + + "1/{instance.name=projects/*/instances/*}" + + ":\001*\022\223\001\n\016DeleteInstance\0227.google.spanner." + + "admin.instance.v1.DeleteInstanceRequest\032" + + "\026.google.protobuf.Empty\"0\332A\004name\202\323\344\223\002#*!" + + "/v1/{name=projects/*/instances/*}\022\232\001\n\014Se" + + "tIamPolicy\022\".google.iam.v1.SetIamPolicyR" + + "equest\032\025.google.iam.v1.Policy\"O\332A\017resour" + + "ce,policy\202\323\344\223\0027\"2/v1/{resource=projects/" + + "*/instances/*}:setIamPolicy:\001*\022\223\001\n\014GetIa" + + "mPolicy\022\".google.iam.v1.GetIamPolicyRequ" + + "est\032\025.google.iam.v1.Policy\"H\332A\010resource\202" + + "\323\344\223\0027\"2/v1/{resource=projects/*/instance" + + "s/*}:getIamPolicy:\001*\022\305\001\n\022TestIamPermissi" + + "ons\022(.google.iam.v1.TestIamPermissionsRe" + + "quest\032).google.iam.v1.TestIamPermissions" + + "Response\"Z\332A\024resource,permissions\202\323\344\223\002=\"" + + "8/v1/{resource=projects/*/instances/*}:t" + + "estIamPermissions:\001*\022\321\001\n\024GetInstancePart" + + "ition\022=.google.spanner.admin.instance.v1" + + ".GetInstancePartitionRequest\0323.google.sp" + + "anner.admin.instance.v1.InstancePartitio" + + "n\"E\332A\004name\202\323\344\223\0028\0226/v1/{name=projects/*/i" + + "nstances/*/instancePartitions/*}\022\351\002\n\027Cre" + + "ateInstancePartition\022@.google.spanner.ad" + + "min.instance.v1.CreateInstancePartitionR" + + "equest\032\035.google.longrunning.Operation\"\354\001" + + "\312Av\n2google.spanner.admin.instance.v1.In" + + "stancePartition\022@google.spanner.admin.in" + + "stance.v1.CreateInstancePartitionMetadat" + + "a\332A/parent,instance_partition,instance_p" + + "artition_id\202\323\344\223\002;\"6/v1/{parent=projects/" + + "*/instances/*}/instancePartitions:\001*\022\272\001\n" + + "\027DeleteInstancePartition\022@.google.spanne" + + "r.admin.instance.v1.DeleteInstancePartit" + + "ionRequest\032\026.google.protobuf.Empty\"E\332A\004n" + + "ame\202\323\344\223\0028*6/v1/{name=projects/*/instance" + + "s/*/instancePartitions/*}\022\352\002\n\027UpdateInst" + + "ancePartition\022@.google.spanner.admin.ins" + + "tance.v1.UpdateInstancePartitionRequest\032" + + "\035.google.longrunning.Operation\"\355\001\312Av\n2go" + + "ogle.spanner.admin.instance.v1.InstanceP" + + "artition\022@google.spanner.admin.instance." + + "v1.UpdateInstancePartitionMetadata\332A\035ins" + + "tance_partition,field_mask\202\323\344\223\002N2I/v1/{i" + + "nstance_partition.name=projects/*/instan" + + "ces/*/instancePartitions/*}:\001*\022\210\002\n\037ListI" + + "nstancePartitionOperations\022H.google.span" + + "ner.admin.instance.v1.ListInstancePartit" + + "ionOperationsRequest\032I.google.spanner.ad" + + "min.instance.v1.ListInstancePartitionOpe" + + "rationsResponse\"P\332A\006parent\202\323\344\223\002A\022?/v1/{p" + + "arent=projects/*/instances/*}/instancePa" + + "rtitionOperations\022\211\002\n\014MoveInstance\0225.goo" + + "gle.spanner.admin.instance.v1.MoveInstan" + + "ceRequest\032\035.google.longrunning.Operation" + + "\"\242\001\312An\n5google.spanner.admin.instance.v1" + + ".MoveInstanceResponse\0225google.spanner.ad" + + "min.instance.v1.MoveInstanceMetadata\202\323\344\223" + + "\002+\"&/v1/{name=projects/*/instances/*}:mo" + + "ve:\001*\032x\312A\026spanner.googleapis.com\322A\\https" + + "://www.googleapis.com/auth/cloud-platfor" + + "m,https://www.googleapis.com/auth/spanne" + + "r.adminB\213\002\n$com.google.spanner.admin.ins" + + "tance.v1B\031SpannerInstanceAdminProtoP\001ZFc" + + "loud.google.com/go/spanner/admin/instanc" + + "e/apiv1/instancepb;instancepb\252\002&Google.C" + + "loud.Spanner.Admin.Instance.V1\312\002&Google\\" + + "Cloud\\Spanner\\Admin\\Instance\\V1\352\002+Google" + + "::Cloud::Spanner::Admin::Instance::V1b\006p" + + "roto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( @@ -721,6 +727,7 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { "CreateTime", "UpdateTime", "Edition", + "DefaultBackupScheduleType", }); internal_static_google_spanner_admin_instance_v1_Instance_LabelsEntry_descriptor = internal_static_google_spanner_admin_instance_v1_Instance_descriptor diff --git a/proto-google-cloud-spanner-admin-instance-v1/src/main/proto/google/spanner/admin/instance/v1/spanner_instance_admin.proto b/proto-google-cloud-spanner-admin-instance-v1/src/main/proto/google/spanner/admin/instance/v1/spanner_instance_admin.proto index ece99467a40..ba6726b31ba 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/src/main/proto/google/spanner/admin/instance/v1/spanner_instance_admin.proto +++ b/proto-google-cloud-spanner-admin-instance-v1/src/main/proto/google/spanner/admin/instance/v1/spanner_instance_admin.proto @@ -972,6 +972,24 @@ message Instance { ENTERPRISE_PLUS = 3; } + // Indicates the default backup behavior for new databases within the + // instance. + enum DefaultBackupScheduleType { + // Not specified. + DEFAULT_BACKUP_SCHEDULE_TYPE_UNSPECIFIED = 0; + + // No default backup schedule will be created automatically on creation of a + // database within the instance. + NONE = 1; + + // A default backup schedule will be created automatically on creation of a + // database within the instance. The default backup schedule creates a full + // backup every 24 hours and retains the backup for a period of 7 days. Once + // created, the default backup schedule can be edited/deleted similar to any + // other backup schedule. + AUTOMATIC = 2; + } + // Required. A unique identifier for the instance, which cannot be changed // after the instance is created. Values are of the form // `projects//instances/[a-z][-a-z0-9]*[a-z0-9]`. The final @@ -1093,6 +1111,18 @@ message Instance { // Optional. The `Edition` of the current instance. Edition edition = 20 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. Controls the default backup behavior for new databases within the + // instance. + // + // Note that `AUTOMATIC` is not permitted for free instances, as backups and + // backup schedules are not allowed for free instances. + // + // In the `GetInstance` or `ListInstances` response, if the value of + // default_backup_schedule_type is unset or NONE, no default backup + // schedule will be created for new databases within the instance. + DefaultBackupScheduleType default_backup_schedule_type = 23 + [(google.api.field_behavior) = OPTIONAL]; } // The request for diff --git a/proto-google-cloud-spanner-executor-v1/pom.xml b/proto-google-cloud-spanner-executor-v1/pom.xml index 95ecf9feb04..0b5ce4b88d8 100644 --- a/proto-google-cloud-spanner-executor-v1/pom.xml +++ b/proto-google-cloud-spanner-executor-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-executor-v1 - 6.79.0 + 6.80.0 proto-google-cloud-spanner-executor-v1 Proto library for google-cloud-spanner com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index f9fde0dbba1..a5355642bde 100644 --- a/proto-google-cloud-spanner-v1/pom.xml +++ b/proto-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.79.0 + 6.80.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.79.0 + 6.80.0 diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index cd1c3174def..52bce5f88f9 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -33,7 +33,7 @@ com.google.cloud google-cloud-spanner - 6.78.0 + 6.79.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 802a4cd2cdd..8b76f69773d 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -32,7 +32,7 @@ com.google.cloud google-cloud-spanner - 6.79.0 + 6.80.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index bf93ebe41cc..13c21ba4bd8 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -34,7 +34,7 @@ com.google.cloud libraries-bom - 26.48.0 + 26.49.0 pom import diff --git a/samples/snippets/src/main/java/com/example/spanner/TracingSample.java b/samples/snippets/src/main/java/com/example/spanner/TracingSample.java index a3d4e5bd072..9678ebfec74 100644 --- a/samples/snippets/src/main/java/com/example/spanner/TracingSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/TracingSample.java @@ -16,6 +16,8 @@ package com.example.spanner; +import com.google.api.MonitoredResource; +import com.google.cloud.MetadataConfig; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; @@ -23,6 +25,7 @@ import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.spi.v1.SpannerRpcViews; +import io.opencensus.common.Duration; import io.opencensus.common.Scope; import io.opencensus.contrib.grpc.metrics.RpcViews; import io.opencensus.contrib.zpages.ZPageHandlers; @@ -32,11 +35,15 @@ import io.opencensus.trace.samplers.Samplers; import java.util.Arrays; -/** This sample demonstrates how to enable opencensus tracing and stats in cloud spanner client. +/** + * This sample demonstrates how to enable opencensus tracing and stats in cloud spanner client. * - * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to enable metrics - * and stats with cloud spanner client. -*/ + * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to enable metrics and stats + * with cloud spanner client. + *

Note: This sample uses System.exit(0) to ensure clean termination because the + * ZPageHandlers HTTP server (localhost:8080/tracez) uses non-daemon threads and does not + * provide a public stop() method. + */ public class TracingSample { private static final String SAMPLE_SPAN = "CloudSpannerSample"; @@ -58,7 +65,13 @@ public static void main(String[] args) throws Exception { .registerSpanNamesForCollection(Arrays.asList(SAMPLE_SPAN)); // Installs an exporter for stack driver stats. - StackdriverStatsExporter.createAndRegister(); + MonitoredResource.Builder builder = MonitoredResource.newBuilder(); + if (MetadataConfig.getProjectId() != null) { + builder.putLabels("project_id", options.getProjectId()); + } + builder.setType("global"); + StackdriverStatsExporter.createAndRegisterWithProjectIdAndMonitoredResource( + options.getProjectId(), Duration.create(60L, 0), builder.build()); RpcViews.registerAllGrpcViews(); // Capture GFE Latency and GFE Header missing count. SpannerRpcViews.registerGfeLatencyAndHeaderMissingCountViews(); @@ -85,8 +98,19 @@ public static void main(String[] args) throws Exception { } } } finally { - // Closes the client which will free up the resources used + // First, shutdown the stats/metrics exporters + StackdriverStatsExporter.unregister(); + + // Shutdown tracing components + StackdriverExporter.unregister(); + Tracing.getExportComponent().shutdown(); + + // Close the spanner client spanner.close(); + + // Force immediate exit since ZPageHandlers.startHttpServerAndRegisterAll(8080) + // starts a non-daemon HTTP server thread that cannot be stopped gracefully + System.exit(0); } } } diff --git a/versions.txt b/versions.txt index 283c348e6e5..3c5a3b05694 100644 --- a/versions.txt +++ b/versions.txt @@ -1,13 +1,13 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.79.0:6.79.0 -proto-google-cloud-spanner-v1:6.79.0:6.79.0 -proto-google-cloud-spanner-admin-database-v1:6.79.0:6.79.0 -grpc-google-cloud-spanner-v1:6.79.0:6.79.0 -grpc-google-cloud-spanner-admin-instance-v1:6.79.0:6.79.0 -grpc-google-cloud-spanner-admin-database-v1:6.79.0:6.79.0 -google-cloud-spanner:6.79.0:6.79.0 -google-cloud-spanner-executor:6.79.0:6.79.0 -proto-google-cloud-spanner-executor-v1:6.79.0:6.79.0 -grpc-google-cloud-spanner-executor-v1:6.79.0:6.79.0 +proto-google-cloud-spanner-admin-instance-v1:6.80.0:6.80.0 +proto-google-cloud-spanner-v1:6.80.0:6.80.0 +proto-google-cloud-spanner-admin-database-v1:6.80.0:6.80.0 +grpc-google-cloud-spanner-v1:6.80.0:6.80.0 +grpc-google-cloud-spanner-admin-instance-v1:6.80.0:6.80.0 +grpc-google-cloud-spanner-admin-database-v1:6.80.0:6.80.0 +google-cloud-spanner:6.80.0:6.80.0 +google-cloud-spanner-executor:6.80.0:6.80.0 +proto-google-cloud-spanner-executor-v1:6.80.0:6.80.0 +grpc-google-cloud-spanner-executor-v1:6.80.0:6.80.0