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