diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index a6177d5b0ef..5b8fc9acc0e 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -19,8 +19,6 @@ branchProtectionRules: - compile (8) - compile (11) - OwlBot Post Processor - - 'Kokoro - Test: Java GraalVM Native Image' - - 'Kokoro - Test: Java 17 GraalVM Native Image' - pattern: 3.3.x isAdminEnforced: true requiredApprovingReviewCount: 1 diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 00d8e29f6dd..2a25656e255 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -88,6 +88,8 @@ integration) -Dclirr.skip=true \ -Denforcer.skip=true \ -Dmaven.main.skip=true \ + -Dspanner.gce.config.project_id=gcloud-devel \ + -Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests \ -fae \ verify RETURN_CODE=$? @@ -126,12 +128,12 @@ integration-cloud-staging) ;; graalvm) # Run Unit and Integration Tests with Native Image - mvn test -Pnative -Penable-integration-tests + mvn test -Pnative -Penable-integration-tests -Dspanner.gce.config.project_id=gcloud-devel -Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests RETURN_CODE=$? ;; graalvm17) # Run Unit and Integration Tests with Native Image - mvn test -Pnative -Penable-integration-tests + mvn test -Pnative -Penable-integration-tests -Dspanner.gce.config.project_id=gcloud-devel -Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests RETURN_CODE=$? ;; slowtests) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6ec58479f..eb350402bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [6.45.1](https://github.com/googleapis/java-spanner/compare/v6.45.0...v6.45.1) (2023-08-11) + + +### Bug Fixes + +* Always allow metadata queries ([#2580](https://github.com/googleapis/java-spanner/issues/2580)) ([ebb17fc](https://github.com/googleapis/java-spanner/commit/ebb17fc8aeac5fc75e4f135f33dba970f2480585)) + ## [6.45.0](https://github.com/googleapis/java-spanner/compare/v6.44.0...v6.45.0) (2023-08-04) diff --git a/README.md b/README.md index 3d15b34d829..8c508d3f93c 100644 --- a/README.md +++ b/README.md @@ -50,20 +50,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.21.0') +implementation platform('com.google.cloud:libraries-bom:26.22.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.44.0' +implementation 'com.google.cloud:google-cloud-spanner:6.45.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.44.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.0" ``` @@ -430,7 +430,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.44.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.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/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index d968cc1ae36..7fbfef43a39 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.45.0 + 6.45.1 pom com.google.cloud @@ -53,48 +53,48 @@ com.google.cloud google-cloud-spanner - 6.45.0 + 6.45.1 com.google.cloud google-cloud-spanner-executor - 6.45.0 + 6.45.1 com.google.cloud google-cloud-spanner test-jar - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.0 + 6.45.1 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.0 + 6.45.1 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.0 + 6.45.1 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.0 + 6.45.1 diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml index 5a5e5aea147..adc61206b41 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.45.0 + 6.45.1 jar Google Cloud Spanner Executor com.google.cloud google-cloud-spanner-parent - 6.45.0 + 6.45.1 diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 24e086cc4d7..2a4c09c66d6 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.45.0 + 6.45.1 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.45.0 + 6.45.1 google-cloud-spanner @@ -122,6 +122,9 @@ -Dspanner.testenv.config.class=${spanner.testenv.config.class} + -Dspanner.testenv.instance=${spanner.testenv.instance} + -Dspanner.gce.config.project_id=${spanner.gce.config.project_id} + -Dspanner.testenv.kms_key.name=${spanner.testenv.kms_key.name} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java index f2230e59bae..b5d4cdf8a2a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java @@ -133,17 +133,6 @@ enum BatchMode { DML } - /** - * This query option is used internally to indicate that a query is executed by the library itself - * to fetch metadata. These queries are specifically allowed to be executed even when a DDL batch - * is active. - */ - static final class InternalMetadataQuery implements QueryOption { - static final InternalMetadataQuery INSTANCE = new InternalMetadataQuery(); - - private InternalMetadataQuery() {} - } - /** The combination of all transaction modes and batch modes. */ enum UnitOfWorkType { READ_ONLY_TRANSACTION { @@ -1219,6 +1208,18 @@ private AsyncResultSet parseAndExecuteQueryAsync( + parsedStatement.getSqlWithoutComments()); } + private boolean isInternalMetadataQuery(QueryOption... options) { + if (options == null) { + return false; + } + for (QueryOption option : options) { + if (option instanceof InternalMetadataQuery) { + return true; + } + } + return false; + } + @Override public long executeUpdate(Statement update) { Preconditions.checkNotNull(update); @@ -1450,8 +1451,11 @@ private ResultSet internalExecuteQuery( || (statement.getType() == StatementType.UPDATE && (analyzeMode != AnalyzeMode.NONE || statement.hasReturningClause())), "Statement must either be a query or a DML mode with analyzeMode!=NONE or returning clause"); - UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork(); - if (autoPartitionMode && statement.getType() == StatementType.QUERY) { + boolean isInternalMetadataQuery = isInternalMetadataQuery(options); + UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork(isInternalMetadataQuery); + if (autoPartitionMode + && statement.getType() == StatementType.QUERY + && !isInternalMetadataQuery) { return runPartitionedQuery( statement.getStatement(), PartitionOptions.getDefaultInstance(), options); } @@ -1475,7 +1479,8 @@ private AsyncResultSet internalExecuteQueryAsync( ConnectionPreconditions.checkState( !(autoPartitionMode && statement.getType() == StatementType.QUERY), "Partitioned queries cannot be executed asynchronously"); - UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork(); + UnitOfWork transaction = + getCurrentUnitOfWorkOrStartNewUnitOfWork(isInternalMetadataQuery(options)); return ResultSets.toAsyncResultSet( transaction.executeQueryAsync( callType, @@ -1514,22 +1519,31 @@ private ApiFuture internalExecuteBatchUpdateAsync( callType, updates, mergeUpdateRequestOptions(mergeUpdateStatementTag(options))); } + private UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() { + return getCurrentUnitOfWorkOrStartNewUnitOfWork(false); + } + /** * Returns the current {@link UnitOfWork} of this connection, or creates a new one based on the * current transaction settings of the connection and returns that. */ @VisibleForTesting - UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() { + UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) { + if (isInternalMetadataQuery) { + // Just return a temporary single-use transaction. + return createNewUnitOfWork(true); + } if (this.currentUnitOfWork == null || !this.currentUnitOfWork.isActive()) { - this.currentUnitOfWork = createNewUnitOfWork(); + this.currentUnitOfWork = createNewUnitOfWork(false); } return this.currentUnitOfWork; } @VisibleForTesting - UnitOfWork createNewUnitOfWork() { - if (isAutocommit() && !isInTransaction() && !isInBatch()) { + UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) { + if (isInternalMetadataQuery || (isAutocommit() && !isInTransaction() && !isInBatch())) { return SingleUseTransaction.newBuilder() + .setInternalMetadataQuery(isInternalMetadataQuery) .setDdlClient(ddlClient) .setDatabaseClient(dbClient) .setBatchClient(batchClient) @@ -1660,7 +1674,7 @@ public void startBatchDdl() { !transactionBeginMarked, "Cannot start a DDL batch when a transaction has begun"); this.batchMode = BatchMode.DDL; this.unitOfWorkType = UnitOfWorkType.DDL_BATCH; - this.currentUnitOfWork = createNewUnitOfWork(); + this.currentUnitOfWork = createNewUnitOfWork(false); } @Override @@ -1678,7 +1692,7 @@ public void startBatchDml() { // Then create the DML batch. this.batchMode = BatchMode.DML; this.unitOfWorkType = UnitOfWorkType.DML_BATCH; - this.currentUnitOfWork = createNewUnitOfWork(); + this.currentUnitOfWork = createNewUnitOfWork(false); } @Override diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java index ebfd2bc4541..55b780c5718 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java @@ -33,15 +33,12 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement; import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType; -import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.spanner.admin.database.v1.DatabaseAdminGrpc; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; -import com.google.spanner.v1.SpannerGrpc; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; @@ -121,29 +118,6 @@ public ApiFuture executeQueryAsync( final ParsedStatement statement, AnalyzeMode analyzeMode, QueryOption... options) { - if (options != null) { - for (int i = 0; i < options.length; i++) { - if (options[i] instanceof InternalMetadataQuery) { - Preconditions.checkNotNull(statement); - Preconditions.checkArgument(statement.isQuery(), "Statement is not a query"); - Preconditions.checkArgument( - analyzeMode == AnalyzeMode.NONE, "Analyze is not allowed for DDL batch"); - // Queries marked with internal metadata queries are allowed during a DDL batch. - // These can only be generated by library internal methods and may be used to check - // whether a database object such as table or an index exists. - List temp = new ArrayList<>(); - Collections.addAll(temp, options); - temp.remove(i); - final QueryOption[] internalOptions = temp.toArray(new QueryOption[0]); - Callable callable = - () -> - DirectExecuteResultSet.ofResultSet( - dbClient.singleUse().executeQuery(statement.getStatement(), internalOptions)); - return executeStatementAsync( - callType, statement, callable, SpannerGrpc.getExecuteStreamingSqlMethod()); - } - } - } // Queries are by default not allowed on DDL batches. throw SpannerExceptionFactory.newSpannerException( ErrorCode.FAILED_PRECONDITION, "Executing queries is not allowed for DDL batches."); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java index ea2159b00a7..52486ed43e1 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java @@ -78,6 +78,7 @@ class SingleUseTransaction extends AbstractBaseUnitOfWork { private final TimestampBound readOnlyStaleness; private final AutocommitDmlMode autocommitDmlMode; private final boolean returnCommitStats; + private final boolean internalMetdataQuery; private volatile SettableApiFuture readTimestamp = null; private volatile TransactionRunner writeTransaction; private boolean used = false; @@ -91,6 +92,7 @@ static class Builder extends AbstractBaseUnitOfWork.Builder executeQueryAsync( return executeDmlReturningAsync(callType, statement, options); } + // Do not use a read-only staleness for internal metadata queries. final ReadOnlyTransaction currentTransaction = - dbClient.singleUseReadOnlyTransaction(readOnlyStaleness); + internalMetdataQuery + ? dbClient.singleUseReadOnlyTransaction() + : dbClient.singleUseReadOnlyTransaction(readOnlyStaleness); Callable callable = () -> { try { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java index 460b1000cee..2c59439ce7d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState; +import com.google.api.client.util.ExponentialBackOff; import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.paging.Page; import com.google.cloud.Timestamp; @@ -25,8 +26,8 @@ import com.google.cloud.spanner.testing.RemoteSpannerHelper; import com.google.common.collect.Iterators; import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; -import io.grpc.Status; import java.util.Random; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,6 +53,9 @@ public class IntegrationTestEnv extends ExternalResource { */ public static final String TEST_INSTANCE_PROPERTY = "spanner.testenv.instance"; + public static final String MAX_CREATE_INSTANCE_ATTEMPTS = + "spanner.testenv.max_create_instance_attempts"; + private static final Logger logger = Logger.getLogger(IntegrationTestEnv.class.getName()); private TestEnvConfig config; @@ -126,9 +130,14 @@ protected void after() { this.config.tearDown(); } - private void initializeInstance(InstanceId instanceId) { - InstanceConfig instanceConfig = - Iterators.get(instanceAdminClient.listInstanceConfigs().iterateAll().iterator(), 0, null); + private void initializeInstance(InstanceId instanceId) throws Exception { + InstanceConfig instanceConfig; + try { + instanceConfig = instanceAdminClient.getInstanceConfig("regional-us-central1"); + } catch (Throwable ignore) { + instanceConfig = + Iterators.get(instanceAdminClient.listInstanceConfigs().iterateAll().iterator(), 0, null); + } checkState(instanceConfig != null, "No instance configs found"); InstanceConfigId configId = instanceConfig.getId(); @@ -142,40 +151,62 @@ private void initializeInstance(InstanceId instanceId) { OperationFuture op = instanceAdminClient.createInstance(instance); Instance createdInstance; + int maxAttempts = 25; try { - createdInstance = op.get(); - } catch (Exception e) { - boolean cancelled = false; + maxAttempts = + Integer.parseInt( + System.getProperty(MAX_CREATE_INSTANCE_ATTEMPTS, String.valueOf(maxAttempts))); + } catch (NumberFormatException ignore) { + // Ignore and fall back to the default. + } + ExponentialBackOff backOff = + new ExponentialBackOff.Builder() + .setInitialIntervalMillis(5_000) + .setMaxIntervalMillis(500_000) + .setMultiplier(2.0) + .build(); + int attempts = 0; + while (true) { try { - // Try to cancel the createInstance operation. - instanceAdminClient.cancelOperation(op.getName()); - com.google.longrunning.Operation createOperation = - instanceAdminClient.getOperation(op.getName()); - cancelled = - createOperation.hasError() - && createOperation.getError().getCode() == Status.CANCELLED.getCode().value(); - if (cancelled) { - logger.info("Cancelled the createInstance operation because the operation failed"); - } else { - logger.info( - "Tried to cancel the createInstance operation because the operation failed, but the operation could not be cancelled. Current status: " - + createOperation.getError().getCode()); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Failed to cancel the createInstance operation", t); - } - if (!cancelled) { - try { - instanceAdminClient.deleteInstance(instanceId.getInstance()); - logger.info( - "Deleted the test instance because the createInstance operation failed and cancelling the operation did not succeed"); - } catch (Throwable t) { - logger.log(Level.WARNING, "Failed to delete the test instance", t); + createdInstance = op.get(); + } catch (Exception e) { + SpannerException spannerException = + (e instanceof ExecutionException && e.getCause() != null) + ? SpannerExceptionFactory.asSpannerException(e.getCause()) + : SpannerExceptionFactory.asSpannerException(e); + if (attempts < maxAttempts && isRetryableResourceExhaustedException(spannerException)) { + attempts++; + if (spannerException.getRetryDelayInMillis() > 0L) { + //noinspection BusyWait + Thread.sleep(spannerException.getRetryDelayInMillis()); + } else { + // The Math.max(...) prevents Backoff#STOP (=-1) to be used as the sleep value. + //noinspection BusyWait + Thread.sleep(Math.max(backOff.getMaxIntervalMillis(), backOff.nextBackOffMillis())); + } + continue; } + throw SpannerExceptionFactory.newSpannerException( + spannerException.getErrorCode(), + String.format( + "Could not create test instance and giving up after %d attempts: %s", + attempts, e.getMessage()), + e); } - throw SpannerExceptionFactory.newSpannerException(e); + logger.log(Level.INFO, "Created test instance: {0}", createdInstance.getId()); + break; + } + } + + static boolean isRetryableResourceExhaustedException(SpannerException exception) { + if (exception.getErrorCode() != ErrorCode.RESOURCE_EXHAUSTED) { + return false; } - logger.log(Level.INFO, "Created test instance: {0}", createdInstance.getId()); + return exception + .getMessage() + .contains( + "Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'") + || exception.getMessage().matches(".*cannot add \\d+ nodes in region.*"); } private void cleanUpOldDatabases(InstanceId instanceId) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnvTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnvTest.java new file mode 100644 index 00000000000..8aa6d550516 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnvTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static com.google.cloud.spanner.IntegrationTestEnv.isRetryableResourceExhaustedException; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class IntegrationTestEnvTest { + + @Test + public void testIsRetryableResourceExhaustedException() { + assertFalse( + isRetryableResourceExhaustedException( + SpannerExceptionFactory.newSpannerException(ErrorCode.INVALID_ARGUMENT, "test"))); + assertFalse( + isRetryableResourceExhaustedException( + SpannerExceptionFactory.newSpannerException(ErrorCode.RESOURCE_EXHAUSTED, "test"))); + assertTrue( + isRetryableResourceExhaustedException( + SpannerExceptionFactory.newSpannerException( + ErrorCode.RESOURCE_EXHAUSTED, + "Operation with name \"projects/my-project/instances/my-instance/operations/32bb3dccf4243afc\" failed with status = GrpcStatusCode{transportCode=RESOURCE_EXHAUSTED} and message = Project 123 cannot add 1 nodes in region ."))); + assertTrue( + isRetryableResourceExhaustedException( + SpannerExceptionFactory.newSpannerException( + ErrorCode.RESOURCE_EXHAUSTED, + "Operation with name \"projects/my-project/instances/my-instance/operations/32bb3dccf4243afc\" failed with status = GrpcStatusCode{transportCode=RESOURCE_EXHAUSTED} and message = Project 123 cannot add 99 nodes in region ."))); + assertTrue( + isRetryableResourceExhaustedException( + SpannerExceptionFactory.newSpannerException( + ErrorCode.RESOURCE_EXHAUSTED, + "Could not create instance. Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'"))); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java index d767c9f5176..09d2f92a6dc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java @@ -1389,7 +1389,7 @@ public void testMergeQueryOptions() { new ConnectionImpl( connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) { @Override - UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() { + UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) { return unitOfWork; } }) { @@ -1498,7 +1498,7 @@ public void testStatementTagAlwaysAllowed() { new ConnectionImpl( connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) { @Override - UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() { + UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) { return unitOfWork; } }) { @@ -1609,7 +1609,7 @@ public void testTransactionTagNotAllowedAfterTransactionStarted() { new ConnectionImpl( connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) { @Override - UnitOfWork createNewUnitOfWork() { + UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) { return unitOfWork; } }) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java index 699ab23f3a4..5ef4c5291d1 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java @@ -40,15 +40,12 @@ import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Mutation; -import com.google.cloud.spanner.ReadContext; -import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SpannerBatchUpdateException; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement; import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType; -import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery; import com.google.cloud.spanner.connection.UnitOfWork.CallType; import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState; import com.google.protobuf.Timestamp; @@ -157,25 +154,6 @@ public void testExecuteCreateDatabase() { .parse(Statement.of("CREATE DATABASE foo")))); } - @Test - public void testExecuteMetadataQuery() { - Statement statement = Statement.of("SELECT * FROM INFORMATION_SCHEMA.TABLES"); - ParsedStatement parsedStatement = mock(ParsedStatement.class); - when(parsedStatement.isQuery()).thenReturn(true); - when(parsedStatement.getStatement()).thenReturn(statement); - DatabaseClient dbClient = mock(DatabaseClient.class); - ReadContext singleUse = mock(ReadContext.class); - ResultSet resultSet = mock(ResultSet.class); - when(singleUse.executeQuery(statement)).thenReturn(resultSet); - when(dbClient.singleUse()).thenReturn(singleUse); - DdlBatch batch = createSubject(createDefaultMockDdlClient(), dbClient); - assertThat( - get(batch.executeQueryAsync( - CallType.SYNC, parsedStatement, AnalyzeMode.NONE, InternalMetadataQuery.INSTANCE)) - .hashCode(), - is(equalTo(resultSet.hashCode()))); - } - @Test public void testExecuteUpdate() { DdlBatch batch = createSubject(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/InternalMetadataQueryMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/InternalMetadataQueryMockServerTest.java new file mode 100644 index 00000000000..89c4dd0b038 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/InternalMetadataQueryMockServerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.connection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.TimestampBound; +import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery; +import com.google.protobuf.ByteString; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.PartitionQueryRequest; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InternalMetadataQueryMockServerTest extends AbstractMockServerTest { + private static final Statement STATEMENT = + Statement.of("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES"); + + @Parameters(name = "dialect = {0}") + public static Object[] data() { + return Dialect.values(); + } + + @Parameter public Dialect dialect; + + @BeforeClass + public static void setupInternalMetadataQueryResults() { + mockSpanner.putStatementResult( + StatementResult.query(STATEMENT, SELECT_COUNT_RESULTSET_BEFORE_INSERT)); + } + + @Before + public void setupDialect() { + mockSpanner.putStatementResult(StatementResult.detectDialectResult(dialect)); + } + + @After + public void clearRequests() { + mockSpanner.clearRequests(); + } + + @Test + public void testInternalMetadataQueryInAutocommit() { + try (Connection connection = createConnection()) { + connection.setAutocommit(true); + verifyInternalMetadataQuery(connection); + } + } + + @Test + public void testInternalMetadataQueryWithStaleness() { + try (Connection connection = createConnection()) { + connection.setAutocommit(true); + connection.setReadOnlyStaleness(TimestampBound.ofMaxStaleness(10L, TimeUnit.SECONDS)); + verifyInternalMetadataQuery(connection); + } + } + + @Test + public void testInternalMetadataQueryReadOnlyTransaction() { + try (Connection connection = createConnection()) { + connection.setAutocommit(false); + connection.setReadOnly(true); + verifyInternalMetadataQuery(connection); + } + } + + @Test + public void testInternalMetadataQueryReadOnlyTransactionWithStaleness() { + try (Connection connection = createConnection()) { + connection.setAutocommit(false); + connection.setReadOnly(true); + connection.setReadOnlyStaleness(TimestampBound.ofExactStaleness(10L, TimeUnit.SECONDS)); + verifyInternalMetadataQuery(connection); + } + } + + @Test + public void testInternalMetadataQueryReadWriteTransaction() { + try (Connection connection = createConnection()) { + connection.setAutocommit(false); + connection.setReadOnly(false); + verifyInternalMetadataQuery(connection); + } + } + + @Test + public void testInternalMetadataQueryInDmlBatch() { + try (Connection connection = createConnection()) { + connection.startBatchDml(); + verifyInternalMetadataQuery(connection); + connection.runBatch(); + } + } + + @Test + public void testInternalMetadataQueryInDdlBatch() { + try (Connection connection = createConnection()) { + connection.startBatchDdl(); + verifyInternalMetadataQuery(connection); + connection.runBatch(); + } + } + + @Test + public void testInternalMetadataQueryInAutoPartitionMode() { + try (Connection connection = createConnection()) { + connection.setAutoPartitionMode(true); + verifyInternalMetadataQuery(connection); + } + } + + private void verifyInternalMetadataQuery(Connection connection) { + try (ResultSet resultSet = connection.executeQuery(STATEMENT, InternalMetadataQuery.INSTANCE)) { + assertTrue(resultSet.next()); + assertEquals(0L, resultSet.getLong(0)); + assertFalse(resultSet.next()); + } + assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)); + ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0); + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasSingleUse()); + assertTrue(request.getTransaction().getSingleUse().hasReadOnly()); + assertTrue(request.getTransaction().getSingleUse().getReadOnly().hasStrong()); + assertEquals(ByteString.EMPTY, request.getPartitionToken()); + assertEquals(0, mockSpanner.countRequestsOfType(PartitionQueryRequest.class)); + } +} diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index 5d47e90a175..560e6133a44 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.45.0 + 6.45.1 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.45.0 + 6.45.1 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index 82573fbd0dc..d70202912ac 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.45.0 + 6.45.1 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.45.0 + 6.45.1 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index 62a646487a2..998a11a850c 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.45.0 + 6.45.1 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.0 + 6.45.1 diff --git a/pom.xml b/pom.xml index 918d823a42d..70a1df1a8aa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.45.0 + 6.45.1 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -62,37 +62,37 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.45.0 + 6.45.1 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.45.0 + 6.45.1 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.45.0 + 6.45.1 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.45.0 + 6.45.1 com.google.cloud google-cloud-spanner - 6.45.0 + 6.45.1 diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index 33785ffa537..16f77341394 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.45.0 + 6.45.1 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.45.0 + 6.45.1 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index 7682d8c6401..6603ea20cd9 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.45.0 + 6.45.1 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.45.0 + 6.45.1 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index 28e4c3f7284..38671171352 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.45.0 + 6.45.1 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.45.0 + 6.45.1 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index ce36315cd7c..5af0327ef23 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.45.0 + 6.45.1 diff --git a/versions.txt b/versions.txt index 8aa93aa450a..240240a1965 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.45.0:6.45.0 -proto-google-cloud-spanner-v1:6.45.0:6.45.0 -proto-google-cloud-spanner-admin-database-v1:6.45.0:6.45.0 -grpc-google-cloud-spanner-v1:6.45.0:6.45.0 -grpc-google-cloud-spanner-admin-instance-v1:6.45.0:6.45.0 -grpc-google-cloud-spanner-admin-database-v1:6.45.0:6.45.0 -google-cloud-spanner:6.45.0:6.45.0 -google-cloud-spanner-executor:6.45.0:6.45.0 +proto-google-cloud-spanner-admin-instance-v1:6.45.1:6.45.1 +proto-google-cloud-spanner-v1:6.45.1:6.45.1 +proto-google-cloud-spanner-admin-database-v1:6.45.1:6.45.1 +grpc-google-cloud-spanner-v1:6.45.1:6.45.1 +grpc-google-cloud-spanner-admin-instance-v1:6.45.1:6.45.1 +grpc-google-cloud-spanner-admin-database-v1:6.45.1:6.45.1 +google-cloud-spanner:6.45.1:6.45.1 +google-cloud-spanner-executor:6.45.1:6.45.1