diff --git a/README.md b/README.md
index cdfaf9f988e..79c9ac3aae7 100644
--- a/README.md
+++ b/README.md
@@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
If you are using Gradle without BOM, add this to your dependencies:
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner:6.66.0'
+implementation 'com.google.cloud:google-cloud-spanner:6.67.0'
```
If you are using SBT, add this to your dependencies:
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.66.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.67.0"
```
@@ -671,7 +671,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.66.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.67.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/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index 8b4c75ad385..b591c0378de 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -337,6 +337,12 @@
com/google/cloud/spanner/spi/v1/GapicSpannerRpc
com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall read(com.google.spanner.v1.ReadRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map)
+
+ 7005
+ com/google/cloud/spanner/spi/v1/GapicSpannerRpc
+ com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(java.lang.String, java.lang.Iterable, java.lang.String)
+ com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(com.google.cloud.spanner.Database, java.lang.Iterable, java.lang.String)
+
7005
com/google/cloud/spanner/spi/v1/GapicSpannerRpc
@@ -387,6 +393,12 @@
com/google/cloud/spanner/spi/v1/SpannerRpc
com.google.cloud.spanner.spi.v1.SpannerRpc$StreamingCall read(com.google.spanner.v1.ReadRequest, com.google.cloud.spanner.spi.v1.SpannerRpc$ResultStreamConsumer, java.util.Map)
+
+ 7005
+ com/google/cloud/spanner/spi/v1/SpannerRpc
+ com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(java.lang.String, java.lang.Iterable, java.lang.String)
+ com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(com.google.cloud.spanner.Database, java.lang.Iterable, java.lang.String)
+
7005
com/google/cloud/spanner/spi/v1/SpannerRpc
@@ -656,7 +668,7 @@
com/google/cloud/spanner/connection/Connection
com.google.cloud.spanner.Spanner getSpanner()
-
+
7012
@@ -668,7 +680,7 @@
com/google/cloud/spanner/connection/Connection
com.google.cloud.spanner.connection.DdlInTransactionMode getDdlInTransactionMode()
-
+
7012
@@ -688,4 +700,16 @@
void setExcludeTxnFromChangeStreams(boolean)
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ byte[] getProtoDescriptors()
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ void setProtoDescriptors(byte[])
+
+
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
index 4d93a9bfb02..748cb7f87ec 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
@@ -147,7 +147,7 @@ public static Type pgOid() {
/**
* To get the descriptor for the {@code PROTO} type.
*
- * @param protoTypeFqn Proto fully qualified name (ex: "spanner.examples.music.SingerInfo").
+ * @param protoTypeFqn Proto fully qualified name (ex: "examples.spanner.music.SingerInfo").
*/
public static Type proto(String protoTypeFqn) {
return new Type(Code.PROTO, protoTypeFqn);
@@ -156,7 +156,7 @@ public static Type proto(String protoTypeFqn) {
/**
* To get the descriptor for the {@code ENUM} type.
*
- * @param protoTypeFqn Proto ENUM fully qualified name (ex: "spanner.examples.music.Genre")
+ * @param protoTypeFqn Proto ENUM fully qualified name (ex: "examples.spanner.music.Genre")
*/
public static Type protoEnum(String protoTypeFqn) {
return new Type(Code.ENUM, protoTypeFqn);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
index 6be277777a6..4e4053249d2 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
@@ -26,10 +26,12 @@
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import com.google.protobuf.Duration;
import com.google.protobuf.util.Durations;
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.RequestOptions.Priority;
+import java.util.Base64;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
@@ -563,4 +565,46 @@ public String convert(String value) {
return value.substring(7).trim();
}
}
+
+ /** Converter for converting Base64 encoded string to byte[] */
+ static class ProtoDescriptorsConverter implements ClientSideStatementValueConverter {
+
+ public ProtoDescriptorsConverter(String allowedValues) {}
+
+ @Override
+ public Class getParameterClass() {
+ return byte[].class;
+ }
+
+ @Override
+ public byte[] convert(String value) {
+ if (value == null || value.length() == 0 || value.equalsIgnoreCase("null")) {
+ return null;
+ }
+ try {
+ return Base64.getDecoder().decode(value);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+ }
+
+ /** Converter for converting String that take in file path as input to String */
+ static class ProtoDescriptorsFileConverter implements ClientSideStatementValueConverter {
+
+ public ProtoDescriptorsFileConverter(String allowedValues) {}
+
+ @Override
+ public Class getParameterClass() {
+ return String.class;
+ }
+
+ @Override
+ public String convert(String filePath) {
+ if (Strings.isNullOrEmpty(filePath)) {
+ return null;
+ }
+ return filePath;
+ }
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
index 59a919ec9c8..0178be3b289 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
@@ -47,6 +47,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
/**
* Internal connection API for Google Cloud Spanner. This interface may introduce breaking changes
@@ -403,6 +404,25 @@ default boolean isExcludeTxnFromChangeStreams() {
throw new UnsupportedOperationException();
}
+ /**
+ * Sets the proto descriptors to use for the next DDL statement (single or batch) that will be
+ * executed. The proto descriptor is automatically cleared after the statement is executed.
+ *
+ * @param protoDescriptors The proto descriptors to use with the next DDL statement (single or
+ * batch) that will be executed on this connection.
+ */
+ default void setProtoDescriptors(@Nonnull byte[] protoDescriptors) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return The proto descriptor that will be used with the next DDL statement (single or batch)
+ * that is executed on this connection.
+ */
+ default byte[] getProtoDescriptors() {
+ throw new UnsupportedOperationException();
+ }
+
/**
* @return true if this connection will automatically retry read/write transactions
* that abort. This method may only be called when the connection is in read/write
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 c4e33b14b6b..c0cbc8c22d5 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
@@ -22,6 +22,7 @@
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.GaxProperties;
+import com.google.cloud.ByteArray;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.AsyncResultSet;
import com.google.cloud.spanner.BatchClient;
@@ -65,6 +66,9 @@
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -81,6 +85,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.threeten.bp.Instant;
@@ -113,7 +118,7 @@ private LeakedConnectionException() {
}
}
- private volatile LeakedConnectionException leakedException;;
+ private volatile LeakedConnectionException leakedException;
private final SpannerPool spannerPool;
private AbstractStatementParser statementParser;
/**
@@ -268,10 +273,11 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
private String transactionTag;
private String statementTag;
-
private boolean excludeTxnFromChangeStreams;
private Duration maxCommitDelay;
+ private byte[] protoDescriptors;
+ private String protoDescriptorsFilePath;
/** Create a connection and register it in the SpannerPool. */
ConnectionImpl(ConnectionOptions options) {
@@ -353,6 +359,7 @@ public Spanner getSpanner() {
private DdlClient createDdlClient() {
return DdlClient.newBuilder()
.setDatabaseAdminClient(spanner.getDatabaseAdminClient())
+ .setProjectId(options.getProjectId())
.setInstanceId(options.getInstanceId())
.setDatabaseName(options.getDatabaseName())
.build();
@@ -763,6 +770,52 @@ public void setExcludeTxnFromChangeStreams(boolean excludeTxnFromChangeStreams)
this.excludeTxnFromChangeStreams = excludeTxnFromChangeStreams;
}
+ @Override
+ public byte[] getProtoDescriptors() {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ if (this.protoDescriptors == null && this.protoDescriptorsFilePath != null) {
+ // Read from file if filepath is valid
+ try {
+ File protoDescriptorsFile = new File(this.protoDescriptorsFilePath);
+ if (!protoDescriptorsFile.isFile()) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ String.format(
+ "File %s is not a valid proto descriptors file", this.protoDescriptorsFilePath));
+ }
+ InputStream pdStream = new FileInputStream(protoDescriptorsFile);
+ this.protoDescriptors = ByteArray.copyFrom(pdStream).toByteArray();
+ } catch (Exception exception) {
+ throw SpannerExceptionFactory.newSpannerException(exception);
+ }
+ }
+ return this.protoDescriptors;
+ }
+
+ @Override
+ public void setProtoDescriptors(@Nonnull byte[] protoDescriptors) {
+ Preconditions.checkNotNull(protoDescriptors);
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ ConnectionPreconditions.checkState(
+ !isBatchActive(), "Proto descriptors cannot be set when a batch is active");
+ this.protoDescriptors = protoDescriptors;
+ this.protoDescriptorsFilePath = null;
+ }
+
+ void setProtoDescriptorsFilePath(@Nonnull String protoDescriptorsFilePath) {
+ Preconditions.checkNotNull(protoDescriptorsFilePath);
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ ConnectionPreconditions.checkState(
+ !isBatchActive(), "Proto descriptors file path cannot be set when a batch is active");
+ this.protoDescriptorsFilePath = protoDescriptorsFilePath;
+ this.protoDescriptors = null;
+ }
+
+ String getProtoDescriptorsFilePath() {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ return this.protoDescriptorsFilePath;
+ }
+
/**
* Throws an {@link SpannerException} with code {@link ErrorCode#FAILED_PRECONDITION} if the
* current state of this connection does not allow changing the setting for retryAbortsInternally.
@@ -1806,6 +1859,7 @@ UnitOfWork createNewUnitOfWork(
.setSpan(
createSpanForUnitOfWork(
statementType == StatementType.DDL ? DDL_STATEMENT : SINGLE_USE_TRANSACTION))
+ .setProtoDescriptors(getProtoDescriptors())
.build();
if (!isInternalMetadataQuery && !forceSingleUse) {
// Reset the transaction options after starting a single-use transaction.
@@ -1862,6 +1916,7 @@ UnitOfWork createNewUnitOfWork(
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.setSpan(createSpanForUnitOfWork(DDL_BATCH))
+ .setProtoDescriptors(getProtoDescriptors())
.build();
default:
}
@@ -1885,7 +1940,11 @@ private void popUnitOfWorkFromTransactionStack() {
}
private ApiFuture executeDdlAsync(CallType callType, ParsedStatement ddl) {
- return getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
+ ApiFuture result = getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
+ // reset proto descriptors after executing a DDL statement
+ this.protoDescriptors = null;
+ this.protoDescriptorsFilePath = null;
+ return result;
}
@Override
@@ -1985,6 +2044,11 @@ public ApiFuture runBatchAsync() {
}
return ApiFutures.immediateFuture(new long[0]);
} finally {
+ if (isDdlBatchActive()) {
+ // reset proto descriptors after executing a DDL batch
+ this.protoDescriptors = null;
+ this.protoDescriptorsFilePath = null;
+ }
this.batchMode = BatchMode.NONE;
setDefaultTransactionOptions();
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
index 2fb63d9db3b..0abebabf84d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
@@ -138,6 +138,14 @@ StatementResult statementSetPgSessionCharacteristicsTransactionMode(
StatementResult statementShowTransactionIsolationLevel();
+ StatementResult statementSetProtoDescriptors(byte[] protoDescriptors);
+
+ StatementResult statementSetProtoDescriptorsFilePath(String filePath);
+
+ StatementResult statementShowProtoDescriptors();
+
+ StatementResult statementShowProtoDescriptorsFilePath();
+
StatementResult statementExplain(String sql);
StatementResult statementShowDataBoostEnabled();
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
index 28cb2c8ae91..412dcce8916 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
@@ -35,6 +35,8 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_STATISTICS_PACKAGE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_VERSION;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_PROTO_DESCRIPTORS;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_PROTO_DESCRIPTORS_FILE_PATH;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READONLY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READ_ONLY_STALENESS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_RETRY_ABORTS_INTERNALLY;
@@ -59,6 +61,8 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_STATISTICS_PACKAGE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_VERSION;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_PROTO_DESCRIPTORS;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_PROTO_DESCRIPTORS_FILE_PATH;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READONLY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READ_ONLY_STALENESS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READ_TIMESTAMP;
@@ -637,6 +641,36 @@ public StatementResult statementRunPartitionedQuery(Statement statement) {
ClientSideStatementType.RUN_PARTITIONED_QUERY);
}
+ @Override
+ public StatementResult statementSetProtoDescriptors(byte[] protoDescriptors) {
+ Preconditions.checkNotNull(protoDescriptors);
+ getConnection().setProtoDescriptors(protoDescriptors);
+ return noResult(SET_PROTO_DESCRIPTORS);
+ }
+
+ @Override
+ public StatementResult statementSetProtoDescriptorsFilePath(String filePath) {
+ Preconditions.checkNotNull(filePath);
+ getConnection().setProtoDescriptorsFilePath(filePath);
+ return noResult(SET_PROTO_DESCRIPTORS_FILE_PATH);
+ }
+
+ @Override
+ public StatementResult statementShowProtoDescriptors() {
+ return resultSet(
+ String.format("%sPROTO_DESCRIPTORS", getNamespace(connection.getDialect())),
+ getConnection().getProtoDescriptors(),
+ SHOW_PROTO_DESCRIPTORS);
+ }
+
+ @Override
+ public StatementResult statementShowProtoDescriptorsFilePath() {
+ return resultSet(
+ String.format("%sPROTO_DESCRIPTORS_FILE_PATH", getNamespace(connection.getDialect())),
+ getConnection().getProtoDescriptorsFilePath(),
+ SHOW_PROTO_DESCRIPTORS_FILE_PATH);
+ }
+
private String processQueryPlan(PlanNode planNode) {
StringBuilder planNodeDescription = new StringBuilder(" : { ");
com.google.protobuf.Struct metadata = planNode.getMetadata();
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 f21c7e2cdeb..4c8a0542696 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
@@ -59,10 +59,12 @@ class DdlBatch extends AbstractBaseUnitOfWork {
private final DatabaseClient dbClient;
private final List statements = new ArrayList<>();
private UnitOfWorkState state = UnitOfWorkState.STARTED;
+ private final byte[] protoDescriptors;
static class Builder extends AbstractBaseUnitOfWork.Builder {
private DdlClient ddlClient;
private DatabaseClient dbClient;
+ private byte[] protoDescriptors;
private Builder() {}
@@ -78,6 +80,11 @@ Builder setDatabaseClient(DatabaseClient client) {
return this;
}
+ Builder setProtoDescriptors(byte[] protoDescriptors) {
+ this.protoDescriptors = protoDescriptors;
+ return this;
+ }
+
@Override
DdlBatch build() {
Preconditions.checkState(ddlClient != null, "No DdlClient specified");
@@ -94,6 +101,7 @@ private DdlBatch(Builder builder) {
super(builder);
this.ddlClient = builder.ddlClient;
this.dbClient = builder.dbClient;
+ this.protoDescriptors = builder.protoDescriptors;
}
@Override
@@ -227,7 +235,7 @@ public ApiFuture runBatchAsync(CallType callType) {
() -> {
try {
OperationFuture operation =
- ddlClient.executeDdl(statements);
+ ddlClient.executeDdl(statements, protoDescriptors);
try {
// Wait until the operation has finished.
getWithStatementTimeout(operation, RUN_BATCH_STATEMENT);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
index fedf60d7a91..7bce1ab78cd 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
@@ -19,6 +19,7 @@
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.SpannerExceptionFactory;
@@ -35,11 +36,13 @@
*/
class DdlClient {
private final DatabaseAdminClient dbAdminClient;
+ private final String projectId;
private final String instanceId;
private final String databaseName;
static class Builder {
private DatabaseAdminClient dbAdminClient;
+ private String projectId;
private String instanceId;
private String databaseName;
@@ -51,6 +54,13 @@ Builder setDatabaseAdminClient(DatabaseAdminClient client) {
return this;
}
+ Builder setProjectId(String projectId) {
+ Preconditions.checkArgument(
+ !Strings.isNullOrEmpty(projectId), "Empty projectId is not allowed");
+ this.projectId = projectId;
+ return this;
+ }
+
Builder setInstanceId(String instanceId) {
Preconditions.checkArgument(
!Strings.isNullOrEmpty(instanceId), "Empty instanceId is not allowed");
@@ -67,6 +77,7 @@ Builder setDatabaseName(String name) {
DdlClient build() {
Preconditions.checkState(dbAdminClient != null, "No DatabaseAdminClient specified");
+ Preconditions.checkState(!Strings.isNullOrEmpty(projectId), "No ProjectId specified");
Preconditions.checkState(!Strings.isNullOrEmpty(instanceId), "No InstanceId specified");
Preconditions.checkArgument(
!Strings.isNullOrEmpty(databaseName), "No database name specified");
@@ -80,6 +91,7 @@ static Builder newBuilder() {
private DdlClient(Builder builder) {
this.dbAdminClient = builder.dbAdminClient;
+ this.projectId = builder.projectId;
this.instanceId = builder.instanceId;
this.databaseName = builder.databaseName;
}
@@ -92,17 +104,24 @@ OperationFuture executeCreateDatabase(
}
/** Execute a single DDL statement. */
- OperationFuture executeDdl(String ddl) {
- return executeDdl(Collections.singletonList(ddl));
+ OperationFuture executeDdl(String ddl, byte[] protoDescriptors) {
+ return executeDdl(Collections.singletonList(ddl), protoDescriptors);
}
/** Execute a list of DDL statements as one operation. */
- OperationFuture executeDdl(List statements) {
+ OperationFuture executeDdl(
+ List statements, byte[] protoDescriptors) {
if (statements.stream().anyMatch(DdlClient::isCreateDatabaseStatement)) {
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT, "CREATE DATABASE is not supported in a DDL batch");
}
- return dbAdminClient.updateDatabaseDdl(instanceId, databaseName, statements, null);
+ Database.Builder dbBuilder =
+ dbAdminClient.newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseName));
+ if (protoDescriptors != null) {
+ dbBuilder.setProtoDescriptors(protoDescriptors);
+ }
+ Database db = dbBuilder.build();
+ return dbAdminClient.updateDatabaseDdl(db, statements, null);
}
/** Returns true if the statement is a `CREATE DATABASE ...` statement. */
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 3eeffae47b0..44f20ccdbd8 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
@@ -84,6 +84,7 @@ class SingleUseTransaction extends AbstractBaseUnitOfWork {
private final boolean returnCommitStats;
private final Duration maxCommitDelay;
private final boolean internalMetdataQuery;
+ private final byte[] protoDescriptors;
private volatile SettableApiFuture readTimestamp = null;
private volatile TransactionRunner writeTransaction;
private boolean used = false;
@@ -99,6 +100,7 @@ static class Builder extends AbstractBaseUnitOfWork.Builder executeDdlAsync(CallType callType, final ParsedStatement
ddlClient.executeCreateDatabase(
ddl.getSqlWithoutComments(), dbClient.getDialect());
} else {
- operation = ddlClient.executeDdl(ddl.getSqlWithoutComments());
+ operation = ddlClient.executeDdl(ddl.getSqlWithoutComments(), protoDescriptors);
}
getWithStatementTimeout(operation, ddl);
state = UnitOfWorkState.COMMITTED;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
index 8488276340d..3ddf32ca5fe 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
@@ -107,6 +107,10 @@ enum ClientSideStatementType {
PARTITION,
RUN_PARTITION,
RUN_PARTITIONED_QUERY,
+ SET_PROTO_DESCRIPTORS,
+ SET_PROTO_DESCRIPTORS_FILE_PATH,
+ SHOW_PROTO_DESCRIPTORS,
+ SHOW_PROTO_DESCRIPTORS_FILE_PATH
}
/**
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResultImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResultImpl.java
index 58a1f7ae1c0..ee5032463cd 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResultImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResultImpl.java
@@ -18,6 +18,7 @@
import static com.google.cloud.spanner.SpannerApiFutures.get;
+import com.google.cloud.ByteArray;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.ResultSets;
@@ -147,6 +148,23 @@ static StatementResult resultSet(
clientSideStatementType);
}
+ /**
+ * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
+ * one BYTES column and one row that is created by a {@link ClientSideStatement}.
+ */
+ static StatementResult resultSet(
+ String name, byte[] values, ClientSideStatementType clientSideStatementType) {
+ return of(
+ ResultSets.forRows(
+ Type.struct(StructField.of(name, Type.bytes())),
+ Collections.singletonList(
+ Struct.newBuilder()
+ .set(name)
+ .to(values != null ? ByteArray.copyFrom(values) : null)
+ .build())),
+ clientSideStatementType);
+ }
+
/** {@link StatementResult} containing no results. */
static StatementResult noResult() {
return new StatementResultImpl((ClientSideStatementType) null);
diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
index a1dbe4566c5..d0c2f7f3a6a 100644
--- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
+++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
@@ -669,6 +669,56 @@
"allowedValues": "(\\d{1,9})",
"converterName": "ClientSideStatementValueConverters$NonNegativeIntegerConverter"
}
+ },
+ {
+ "name": "SET PROTO_DESCRIPTORS = ''",
+ "executorName": "ClientSideStatementSetExecutor",
+ "resultType": "NO_RESULT",
+ "statementType": "SET_PROTO_DESCRIPTORS",
+ "regex": "(?is)\\A\\s*set\\s+proto_descriptors\\s*(?:=)\\s*(.*)\\z",
+ "method": "statementSetProtoDescriptors",
+ "exampleStatements": ["set proto_descriptors='protodescriptorsbase64'"],
+ "setStatement": {
+ "propertyName": "PROTO_DESCRIPTORS",
+ "separator": "=",
+ "allowedValues": "'((\\S+)|())'",
+ "converterName": "ClientSideStatementValueConverters$ProtoDescriptorsConverter"
+ }
+ },
+ {
+ "name": "SET PROTO_DESCRIPTORS_FILE_PATH = ''",
+ "executorName": "ClientSideStatementSetExecutor",
+ "resultType": "NO_RESULT",
+ "statementType": "SET_PROTO_DESCRIPTORS_FILE_PATH",
+ "regex": "(?is)\\A\\s*set\\s+proto_descriptors_file_path\\s*(?:=)\\s*(.*)\\z",
+ "method": "statementSetProtoDescriptorsFilePath",
+ "exampleStatements": ["set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'"],
+ "setStatement": {
+ "propertyName": "PROTO_DESCRIPTORS_FILE_PATH",
+ "separator": "=",
+ "allowedValues": "'((\\S+)|())'",
+ "converterName": "ClientSideStatementValueConverters$ProtoDescriptorsFileConverter"
+ }
+ },
+ {
+ "name": "SHOW VARIABLE PROTO_DESCRIPTORS",
+ "executorName": "ClientSideStatementNoParamExecutor",
+ "resultType": "RESULT_SET",
+ "statementType": "SHOW_PROTO_DESCRIPTORS",
+ "regex": "(?is)\\A\\s*show\\s+variable\\s+proto_descriptors\\s*\\z",
+ "method": "statementShowProtoDescriptors",
+ "exampleStatements": ["show variable proto_descriptors"]
+ },
+ {
+ "name": "SHOW VARIABLE PROTO_DESCRIPTORS_FILE_PATH",
+ "executorName": "ClientSideStatementNoParamExecutor",
+ "resultType": "RESULT_SET",
+ "statementType": "SHOW_PROTO_DESCRIPTORS_FILE_PATH",
+ "regex": "(?is)\\A\\s*show\\s+variable\\s+proto_descriptors_file_path\\s*\\z",
+ "method": "statementShowProtoDescriptorsFilePath",
+ "exampleStatements": [
+ "show variable proto_descriptors_file_path"
+ ]
}
]
}
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 a14f91a4815..299205bafc6 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
@@ -25,6 +25,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -32,6 +33,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anyString;
@@ -77,9 +79,11 @@
import com.google.cloud.spanner.connection.UnitOfWork.CallType;
import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState;
import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.ResultSetStats;
+import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -220,8 +224,8 @@ private static DdlClient createDefaultMockDdlClient() {
UpdateDatabaseDdlMetadata metadata = UpdateDatabaseDdlMetadata.getDefaultInstance();
ApiFuture futureMetadata = ApiFutures.immediateFuture(metadata);
when(operation.getMetadata()).thenReturn(futureMetadata);
- when(ddlClient.executeDdl(anyString())).thenCallRealMethod();
- when(ddlClient.executeDdl(anyList())).thenReturn(operation);
+ when(ddlClient.executeDdl(anyString(), isNull())).thenCallRealMethod();
+ when(ddlClient.executeDdl(anyList(), isNull())).thenReturn(operation);
return ddlClient;
} catch (Exception e) {
throw new RuntimeException(e);
@@ -1925,4 +1929,101 @@ private void assertThrowResultNotAllowed(
.contains(
"Only statements that return a result of one of the following types are allowed"));
}
+
+ @Test
+ public void testProtoDescriptorsAlwaysAllowed() {
+ ConnectionOptions connectionOptions = mock(ConnectionOptions.class);
+ when(connectionOptions.isAutocommit()).thenReturn(true);
+ SpannerPool spannerPool = mock(SpannerPool.class);
+ DdlClient ddlClient = mock(DdlClient.class);
+ DatabaseClient dbClient = mock(DatabaseClient.class);
+ when(dbClient.getDialect()).thenReturn(Dialect.GOOGLE_STANDARD_SQL);
+ final UnitOfWork unitOfWork = mock(UnitOfWork.class);
+ final String protoDescriptorsFilePath =
+ "src/test/resources/com/google/cloud/spanner/descriptors.pb";
+ when(unitOfWork.executeDdlAsync(any(), any(ParsedStatement.class)))
+ .thenReturn(ApiFutures.immediateFuture(null));
+ when(unitOfWork.executeQueryAsync(
+ any(), any(ParsedStatement.class), any(AnalyzeMode.class), Mockito.any()))
+ .thenReturn(ApiFutures.immediateFuture(mock(ResultSet.class)));
+ try (ConnectionImpl connection =
+ new ConnectionImpl(
+ connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) {
+ @Override
+ UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(
+ StatementType statementType, boolean isInternalMetadataQuery) {
+ return unitOfWork;
+ }
+ }) {
+ byte[] protoDescriptors;
+ try {
+ InputStream in =
+ ConnectionImplTest.class
+ .getClassLoader()
+ .getResourceAsStream("com/google/cloud/spanner/descriptors.pb");
+ assertNotNull(in);
+ protoDescriptors = ByteStreams.toByteArray(in);
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.newSpannerException(e);
+ }
+
+ assertTrue(connection.isAutocommit());
+
+ assertNull(connection.getProtoDescriptors());
+ connection.setProtoDescriptors(protoDescriptors);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+
+ connection.setAutocommit(false);
+
+ connection.setProtoDescriptors(protoDescriptors);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+
+ // proto descriptor should reset after executing a DDL statement
+ connection.setProtoDescriptors(protoDescriptors);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("CREATE PROTO BUNDLE (examples.spanner.music.SingerInfo)"));
+ assertNull(connection.getProtoDescriptors());
+
+ // proto descriptor should not reset if the statement is not a DDL statement
+ connection.setProtoDescriptors(protoDescriptors);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("SELECT FOO FROM BAR"));
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+
+ // proto descriptor file path should reset after executing a DDL statement
+ connection.setProtoDescriptorsFilePath(protoDescriptorsFilePath);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("CREATE PROTO BUNDLE (examples.spanner.music.SingerInfo)"));
+ assertNull(connection.getProtoDescriptors());
+ assertNull(connection.getProtoDescriptorsFilePath());
+
+ // proto descriptor file path should not reset if the statement is not a DDL statement
+ connection.setProtoDescriptorsFilePath(protoDescriptorsFilePath);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("SELECT FOO FROM BAR"));
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ assertEquals(protoDescriptorsFilePath, connection.getProtoDescriptorsFilePath());
+
+ // test proto descriptor file path as input
+ connection.setProtoDescriptorsFilePath(protoDescriptorsFilePath);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("CREATE PROTO BUNDLE (examples.spanner.music.SingerInfo)"));
+ assertNull(connection.getProtoDescriptors());
+
+ // proto descriptor set through file path should overwrite the proto descriptor set from
+ // byte[]
+ connection.setProtoDescriptors("protoDescriptors".getBytes());
+ connection.setProtoDescriptorsFilePath(protoDescriptorsFilePath);
+ assertArrayEquals(protoDescriptors, connection.getProtoDescriptors());
+ connection.execute(Statement.of("CREATE PROTO BUNDLE (examples.spanner.music.SingerInfo)"));
+ assertNull(connection.getProtoDescriptors());
+
+ // proto descriptor set through byte[] should overwrite the proto descriptor from file path
+ connection.setProtoDescriptorsFilePath(protoDescriptorsFilePath);
+ connection.setProtoDescriptors("protoDescriptors".getBytes());
+ assertArrayEquals("protoDescriptors".getBytes(), connection.getProtoDescriptors());
+ connection.execute(Statement.of("CREATE PROTO BUNDLE (examples.spanner.music.SingerInfo)"));
+ assertNull(connection.getProtoDescriptors());
+ }
+ }
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java
index bf7822e3285..3e883c377fe 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java
@@ -252,4 +252,29 @@ public void testStatementSetPgTransactionModeNoOp() {
verify(connection, never()).setTransactionMode(TransactionMode.READ_ONLY_TRANSACTION);
verify(connection, never()).setTransactionMode(TransactionMode.READ_WRITE_TRANSACTION);
}
+
+ @Test
+ public void testStatementSetProtoDescriptors() {
+ subject.statementSetProtoDescriptors("protoDescriptor".getBytes());
+ verify(connection).setProtoDescriptors("protoDescriptor".getBytes());
+ }
+
+ @Test
+ public void testStatementSetProtoDescriptorsFilePath() {
+ String filePath = "com/google/cloud/spanner/descriptors.pb";
+ subject.statementSetProtoDescriptorsFilePath(filePath);
+ verify(connection).setProtoDescriptorsFilePath(filePath);
+ }
+
+ @Test
+ public void testStatementGetProtoDescriptors() {
+ subject.statementShowProtoDescriptors();
+ verify(connection).getProtoDescriptors();
+ }
+
+ @Test
+ public void testStatementGetProtoDescriptorsFilePath() {
+ subject.statementShowProtoDescriptorsFilePath();
+ verify(connection).getProtoDescriptorsFilePath();
+ }
}
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 74cc925ee85..b6a12567101 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
@@ -21,9 +21,12 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.argThat;
@@ -48,10 +51,12 @@
import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
import com.google.cloud.spanner.connection.UnitOfWork.CallType;
import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState;
+import com.google.common.io.ByteStreams;
import com.google.protobuf.Timestamp;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import io.grpc.Status;
import io.opentelemetry.api.trace.Span;
+import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -108,8 +113,8 @@ private DdlClient createDefaultMockDdlClient(
ApiFuture metadataFuture =
ApiFutures.immediateFuture(metadataBuilder.build());
when(operation.getMetadata()).thenReturn(metadataFuture);
- when(ddlClient.executeDdl(anyString())).thenReturn(operation);
- when(ddlClient.executeDdl(anyList())).thenReturn(operation);
+ when(ddlClient.executeDdl(anyString(), any())).thenReturn(operation);
+ when(ddlClient.executeDdl(anyList(), any())).thenReturn(operation);
return ddlClient;
} catch (Exception e) {
throw new RuntimeException(e);
@@ -253,7 +258,7 @@ public void testGetStateAndIsActive() {
DdlClient client = mock(DdlClient.class);
SpannerException exception = mock(SpannerException.class);
when(exception.getErrorCode()).thenReturn(ErrorCode.FAILED_PRECONDITION);
- doThrow(exception).when(client).executeDdl(anyList());
+ doThrow(exception).when(client).executeDdl(anyList(), isNull());
batch = createSubject(client);
assertThat(batch.getState(), is(UnitOfWorkState.STARTED));
assertThat(batch.isActive(), is(true));
@@ -299,8 +304,8 @@ public void testRunBatch() {
DdlBatch batch = createSubject(client);
get(batch.runBatchAsync(CallType.SYNC));
assertThat(batch.getState(), is(UnitOfWorkState.RAN));
- verify(client, never()).executeDdl(anyString());
- verify(client, never()).executeDdl(argThat(isEmptyListOfStrings()));
+ verify(client, never()).executeDdl(anyString(), isNull());
+ verify(client, never()).executeDdl(argThat(isEmptyListOfStrings()), isNull());
ParsedStatement statement = mock(ParsedStatement.class);
when(statement.getType()).thenReturn(StatementType.DDL);
@@ -311,14 +316,14 @@ public void testRunBatch() {
batch = createSubject(client);
batch.executeDdlAsync(CallType.SYNC, statement);
get(batch.runBatchAsync(CallType.SYNC));
- verify(client).executeDdl(argThat(isListOfStringsWithSize(1)));
+ verify(client).executeDdl(argThat(isListOfStringsWithSize(1)), isNull());
client = createDefaultMockDdlClient();
batch = createSubject(client);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.executeDdlAsync(CallType.SYNC, statement);
get(batch.runBatchAsync(CallType.SYNC));
- verify(client).executeDdl(argThat(isListOfStringsWithSize(2)));
+ verify(client).executeDdl(argThat(isListOfStringsWithSize(2)), isNull());
assertThat(batch.getState(), is(UnitOfWorkState.RAN));
boolean exception = false;
try {
@@ -364,7 +369,48 @@ public void testRunBatch() {
}
assertThat(exception, is(true));
assertThat(batch.getState(), is(UnitOfWorkState.RUN_FAILED));
- verify(client).executeDdl(argThat(isListOfStringsWithSize(2)));
+ verify(client).executeDdl(argThat(isListOfStringsWithSize(2)), isNull());
+
+ // verify when protoDescriptors is null
+ client = createDefaultMockDdlClient();
+ batch =
+ DdlBatch.newBuilder()
+ .setDdlClient(client)
+ .setDatabaseClient(mock(DatabaseClient.class))
+ .withStatementExecutor(new StatementExecutor())
+ .setSpan(Span.getInvalid())
+ .setProtoDescriptors(null)
+ .build();
+ batch.executeDdlAsync(CallType.SYNC, statement);
+ batch.executeDdlAsync(CallType.SYNC, statement);
+ get(batch.runBatchAsync(CallType.SYNC));
+ verify(client).executeDdl(argThat(isListOfStringsWithSize(2)), isNull());
+
+ // verify when protoDescriptors is not null
+ byte[] protoDescriptors;
+ try {
+ InputStream in =
+ DdlBatchTest.class
+ .getClassLoader()
+ .getResourceAsStream("com/google/cloud/spanner/descriptors.pb");
+ assertNotNull(in);
+ protoDescriptors = ByteStreams.toByteArray(in);
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.newSpannerException(e);
+ }
+ client = createDefaultMockDdlClient();
+ batch =
+ DdlBatch.newBuilder()
+ .setDdlClient(client)
+ .setDatabaseClient(mock(DatabaseClient.class))
+ .withStatementExecutor(new StatementExecutor())
+ .setSpan(Span.getInvalid())
+ .setProtoDescriptors(protoDescriptors)
+ .build();
+ batch.executeDdlAsync(CallType.SYNC, statement);
+ batch.executeDdlAsync(CallType.SYNC, statement);
+ get(batch.runBatchAsync(CallType.SYNC));
+ verify(client).executeDdl(argThat(isListOfStringsWithSize(2)), any(byte[].class));
}
@Test
@@ -383,7 +429,8 @@ public void testUpdateCount() throws InterruptedException, ExecutionException {
OperationFuture operationFuture = mock(OperationFuture.class);
when(operationFuture.get()).thenReturn(null);
when(operationFuture.getMetadata()).thenReturn(metadataFuture);
- when(client.executeDdl(argThat(isListOfStringsWithSize(2)))).thenReturn(operationFuture);
+ when(client.executeDdl(argThat(isListOfStringsWithSize(2)), isNull()))
+ .thenReturn(operationFuture);
DdlBatch batch =
DdlBatch.newBuilder()
.withStatementExecutor(new StatementExecutor())
@@ -422,7 +469,8 @@ public void testFailedUpdateCount() throws InterruptedException, ExecutionExcept
new ExecutionException(
"ddl statement failed", Status.INVALID_ARGUMENT.asRuntimeException()));
when(operationFuture.getMetadata()).thenReturn(metadataFuture);
- when(client.executeDdl(argThat(isListOfStringsWithSize(2)))).thenReturn(operationFuture);
+ when(client.executeDdl(argThat(isListOfStringsWithSize(2)), isNull()))
+ .thenReturn(operationFuture);
DdlBatch batch =
DdlBatch.newBuilder()
.withStatementExecutor(new StatementExecutor())
@@ -465,7 +513,8 @@ public void testFailedAfterFirstStatement() throws InterruptedException, Executi
new ExecutionException(
"ddl statement failed", Status.INVALID_ARGUMENT.asRuntimeException()));
when(operationFuture.getMetadata()).thenReturn(metadataFuture);
- when(client.executeDdl(argThat(isListOfStringsWithSize(2)))).thenReturn(operationFuture);
+ when(client.executeDdl(argThat(isListOfStringsWithSize(2)), isNull()))
+ .thenReturn(operationFuture);
DdlBatch batch =
DdlBatch.newBuilder()
.withStatementExecutor(new StatementExecutor())
@@ -497,8 +546,8 @@ public void testAbort() {
DdlBatch batch = createSubject(client);
batch.abortBatch();
assertThat(batch.getState(), is(UnitOfWorkState.ABORTED));
- verify(client, never()).executeDdl(anyString());
- verify(client, never()).executeDdl(anyList());
+ verify(client, never()).executeDdl(anyString(), isNull());
+ verify(client, never()).executeDdl(anyList(), isNull());
ParsedStatement statement = mock(ParsedStatement.class);
when(statement.getType()).thenReturn(StatementType.DDL);
@@ -509,21 +558,21 @@ public void testAbort() {
batch = createSubject(client);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.abortBatch();
- verify(client, never()).executeDdl(anyList());
+ verify(client, never()).executeDdl(anyList(), isNull());
client = createDefaultMockDdlClient();
batch = createSubject(client);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.abortBatch();
- verify(client, never()).executeDdl(anyList());
+ verify(client, never()).executeDdl(anyList(), isNull());
client = createDefaultMockDdlClient();
batch = createSubject(client);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.executeDdlAsync(CallType.SYNC, statement);
batch.abortBatch();
- verify(client, never()).executeDdl(anyList());
+ verify(client, never()).executeDdl(anyList(), isNull());
boolean exception = false;
try {
get(batch.runBatchAsync(CallType.SYNC));
@@ -534,7 +583,7 @@ public void testAbort() {
exception = true;
}
assertThat(exception, is(true));
- verify(client, never()).executeDdl(anyList());
+ verify(client, never()).executeDdl(anyList(), isNull());
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTests.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTests.java
index d46e4dca592..c61635fce23 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTests.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTests.java
@@ -17,17 +17,25 @@
package com.google.cloud.spanner.connection;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.gax.longrunning.OperationFuture;
+import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.common.io.ByteStreams;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
+import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -39,11 +47,13 @@
@RunWith(JUnit4.class)
public class DdlClientTests {
+ private final String projectId = "test-project";
private final String instanceId = "test-instance";
private final String databaseId = "test-database";
private DdlClient createSubject(DatabaseAdminClient client) {
return DdlClient.newBuilder()
+ .setProjectId(projectId)
.setInstanceId(instanceId)
.setDatabaseName(databaseId)
.setDatabaseAdminClient(client)
@@ -52,21 +62,48 @@ private DdlClient createSubject(DatabaseAdminClient client) {
@Test
public void testExecuteDdl() throws InterruptedException, ExecutionException {
+ byte[] protoDescriptors;
+ try {
+ InputStream in =
+ DdlBatchTest.class
+ .getClassLoader()
+ .getResourceAsStream("com/google/cloud/spanner/descriptors.pb");
+ assertNotNull(in);
+ protoDescriptors = ByteStreams.toByteArray(in);
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.newSpannerException(e);
+ }
+
DatabaseAdminClient client = mock(DatabaseAdminClient.class);
+ Database database = mock(Database.class);
+ Database.Builder databaseBuilder = mock(Database.Builder.class);
@SuppressWarnings("unchecked")
OperationFuture operation = mock(OperationFuture.class);
+
when(operation.get()).thenReturn(null);
- when(client.updateDatabaseDdl(eq(instanceId), eq(databaseId), anyList(), isNull()))
- .thenReturn(operation);
+ when(client.newDatabaseBuilder((DatabaseId.of(projectId, instanceId, databaseId))))
+ .thenReturn(databaseBuilder);
+ when(databaseBuilder.setProtoDescriptors(protoDescriptors)).thenReturn(databaseBuilder);
+ when(databaseBuilder.build()).thenReturn(database);
+ when(client.updateDatabaseDdl(eq(database), anyList(), isNull())).thenReturn(operation);
+
DdlClient subject = createSubject(client);
String ddl = "CREATE TABLE FOO";
- subject.executeDdl(ddl);
- verify(client).updateDatabaseDdl(instanceId, databaseId, Collections.singletonList(ddl), null);
+ subject.executeDdl(ddl, null);
+ verify(databaseBuilder, never()).setProtoDescriptors(any(byte[].class));
+ verify(client).updateDatabaseDdl(database, Collections.singletonList(ddl), null);
subject = createSubject(client);
List ddlList = Arrays.asList("CREATE TABLE FOO", "DROP TABLE FOO");
- subject.executeDdl(ddlList);
- verify(client).updateDatabaseDdl(instanceId, databaseId, ddlList, null);
+ subject.executeDdl(ddlList, null);
+ verify(databaseBuilder, never()).setProtoDescriptors(any(byte[].class));
+ verify(client).updateDatabaseDdl(database, ddlList, null);
+
+ subject = createSubject(client);
+ ddlList = Arrays.asList("CREATE PROTO BUNDLE", "CREATE TABLE FOO");
+ subject.executeDdl(ddlList, protoDescriptors);
+ verify(databaseBuilder).setProtoDescriptors(protoDescriptors);
+ verify(client).updateDatabaseDdl(database, ddlList, null);
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsConverterTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsConverterTest.java
new file mode 100644
index 00000000000..766bc987d37
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsConverterTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.connection;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.connection.ClientSideStatementImpl.CompileException;
+import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.ProtoDescriptorsConverter;
+import com.google.common.io.ByteStreams;
+import java.io.InputStream;
+import java.util.Base64;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ProtoDescriptorsConverterTest {
+ @Test
+ public void testConvert() throws CompileException {
+ String allowedValues =
+ ReadOnlyStalenessConverterTest.getAllowedValues(
+ ProtoDescriptorsConverter.class, Dialect.GOOGLE_STANDARD_SQL);
+ assertNotNull(allowedValues);
+ ProtoDescriptorsConverter converter = new ProtoDescriptorsConverter(allowedValues);
+
+ byte[] protoDescriptors;
+ try {
+ InputStream in =
+ ProtoDescriptorsConverterTest.class
+ .getClassLoader()
+ .getResourceAsStream("com/google/cloud/spanner/descriptors.pb");
+ assertNotNull(in);
+ protoDescriptors = ByteStreams.toByteArray(in);
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.newSpannerException(e);
+ }
+
+ assertNull(converter.convert(""));
+ assertNull(converter.convert("null"));
+ assertNull(converter.convert(null));
+ assertNull(converter.convert("random string"));
+
+ assertArrayEquals(
+ converter.convert(Base64.getEncoder().encodeToString(protoDescriptors)), protoDescriptors);
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsFileConverterTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsFileConverterTest.java
new file mode 100644
index 00000000000..29e4a2b591b
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ProtoDescriptorsFileConverterTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.connection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.connection.ClientSideStatementImpl.CompileException;
+import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.ProtoDescriptorsFileConverter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ProtoDescriptorsFileConverterTest {
+ @Test
+ public void testConvert() throws CompileException {
+ String allowedValues =
+ ReadOnlyStalenessConverterTest.getAllowedValues(
+ ProtoDescriptorsFileConverter.class, Dialect.GOOGLE_STANDARD_SQL);
+ assertNotNull(allowedValues);
+ ProtoDescriptorsFileConverter converter = new ProtoDescriptorsFileConverter(allowedValues);
+
+ assertNull(converter.convert(""));
+ assertNull(converter.convert(null));
+
+ String filePath = "com/google/cloud/spanner/descriptors.pb";
+ assertEquals(converter.convert(filePath), filePath);
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
index 0ab419f294b..1fd6d6bd7e2 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
@@ -57,9 +58,11 @@
import com.google.cloud.spanner.connection.StatementExecutor.StatementTimeout;
import com.google.cloud.spanner.connection.UnitOfWork.CallType;
import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.v1.ResultSetStats;
import io.opentelemetry.api.trace.Span;
+import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
@@ -301,8 +304,8 @@ private DdlClient createDefaultMockDdlClient() {
final OperationFuture operation =
mock(OperationFuture.class);
when(operation.get()).thenReturn(null);
- when(ddlClient.executeDdl(anyString())).thenCallRealMethod();
- when(ddlClient.executeDdl(anyList())).thenReturn(operation);
+ when(ddlClient.executeDdl(anyString(), any())).thenCallRealMethod();
+ when(ddlClient.executeDdl(anyList(), any())).thenReturn(operation);
return ddlClient;
} catch (Exception e) {
throw new RuntimeException(e);
@@ -316,7 +319,8 @@ private SingleUseTransaction createSubject() {
TimestampBound.strong(),
AutocommitDmlMode.TRANSACTIONAL,
CommitBehavior.SUCCEED,
- 0L);
+ 0L,
+ null);
}
private SingleUseTransaction createSubject(AutocommitDmlMode dmlMode) {
@@ -326,7 +330,8 @@ private SingleUseTransaction createSubject(AutocommitDmlMode dmlMode) {
TimestampBound.strong(),
dmlMode,
CommitBehavior.SUCCEED,
- 0L);
+ 0L,
+ null);
}
private SingleUseTransaction createSubject(CommitBehavior commitBehavior) {
@@ -336,7 +341,8 @@ private SingleUseTransaction createSubject(CommitBehavior commitBehavior) {
TimestampBound.strong(),
AutocommitDmlMode.TRANSACTIONAL,
commitBehavior,
- 0L);
+ 0L,
+ null);
}
private SingleUseTransaction createDdlSubject(DdlClient ddlClient) {
@@ -346,7 +352,20 @@ private SingleUseTransaction createDdlSubject(DdlClient ddlClient) {
TimestampBound.strong(),
AutocommitDmlMode.TRANSACTIONAL,
CommitBehavior.SUCCEED,
- 0L);
+ 0L,
+ null);
+ }
+
+ private SingleUseTransaction createProtoDescriptorsSubject(
+ DdlClient ddlClient, byte[] protoDescriptors) {
+ return createSubject(
+ ddlClient,
+ false,
+ TimestampBound.strong(),
+ AutocommitDmlMode.TRANSACTIONAL,
+ CommitBehavior.SUCCEED,
+ 0L,
+ protoDescriptors);
}
private SingleUseTransaction createReadOnlySubject(TimestampBound staleness) {
@@ -356,7 +375,8 @@ private SingleUseTransaction createReadOnlySubject(TimestampBound staleness) {
staleness,
AutocommitDmlMode.TRANSACTIONAL,
CommitBehavior.SUCCEED,
- 0L);
+ 0L,
+ null);
}
private SingleUseTransaction createSubject(
@@ -365,7 +385,8 @@ private SingleUseTransaction createSubject(
TimestampBound staleness,
AutocommitDmlMode dmlMode,
final CommitBehavior commitBehavior,
- long timeout) {
+ long timeout,
+ byte[] protoDescriptors) {
DatabaseClient dbClient = mock(DatabaseClient.class);
com.google.cloud.spanner.ReadOnlyTransaction singleUse =
new SimpleReadOnlyTransaction(staleness);
@@ -454,6 +475,7 @@ public TransactionRunner allowNestedTransaction() {
timeout == 0L ? nullTimeout() : timeout(timeout, TimeUnit.MILLISECONDS))
.withStatementExecutor(executor)
.setSpan(Span.getInvalid())
+ .setProtoDescriptors(protoDescriptors)
.build();
}
@@ -541,7 +563,34 @@ public void testExecuteDdl() {
DdlClient ddlClient = createDefaultMockDdlClient();
SingleUseTransaction subject = createDdlSubject(ddlClient);
get(subject.executeDdlAsync(CallType.SYNC, ddl));
- verify(ddlClient).executeDdl(sql);
+ verify(ddlClient).executeDdl(sql, null);
+ }
+
+ @Test
+ public void testExecuteDdlWithProtoDescriptors() {
+ String sql = "CREATE TABLE FOO";
+ ParsedStatement ddl = createParsedDdl(sql);
+ DdlClient ddlClient = createDefaultMockDdlClient();
+ // verify when protoDescriptors value is null
+ SingleUseTransaction subject = createProtoDescriptorsSubject(ddlClient, null);
+ get(subject.executeDdlAsync(CallType.SYNC, ddl));
+ verify(ddlClient).executeDdl(sql, null);
+
+ // verify when protoDescriptors value is not null
+ byte[] protoDescriptors;
+ try {
+ InputStream in =
+ SingleUseTransactionTest.class
+ .getClassLoader()
+ .getResourceAsStream("com/google/cloud/spanner/descriptors.pb");
+ assertNotNull(in);
+ protoDescriptors = ByteStreams.toByteArray(in);
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.newSpannerException(e);
+ }
+ subject = createProtoDescriptorsSubject(ddlClient, protoDescriptors);
+ get(subject.executeDdlAsync(CallType.SYNC, ddl));
+ verify(ddlClient).executeDdl(sql, protoDescriptors);
}
@Test
@@ -735,7 +784,7 @@ public void testMultiUse() {
DdlClient ddlClient = createDefaultMockDdlClient();
SingleUseTransaction subject = createDdlSubject(ddlClient);
get(subject.executeDdlAsync(CallType.SYNC, ddl));
- verify(ddlClient).executeDdl(sql);
+ verify(ddlClient).executeDdl(sql, null);
try {
get(subject.executeDdlAsync(CallType.SYNC, ddl));
fail("missing expected exception");
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementResultImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementResultImplTest.java
index c28d3b75b95..0a55ff2c420 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementResultImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementResultImplTest.java
@@ -21,9 +21,13 @@
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
+import com.google.cloud.ByteArray;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.ResultSet;
@@ -154,6 +158,18 @@ public void testStringResultSetGetResultSet() {
assertThat(subject.getResultSet().next(), is(true));
assertThat(subject.getResultSet().getString("foo"), is(equalTo("bar")));
assertThat(subject.getResultSet().next(), is(false));
+
+ subject =
+ StatementResultImpl.resultSet(
+ "path", "descriptors.pb", ClientSideStatementType.SHOW_PROTO_DESCRIPTORS_FILE_PATH);
+ assertThat(subject.getResultType(), is(equalTo(ResultType.RESULT_SET)));
+ assertThat(
+ subject.getClientSideStatementType(),
+ is(equalTo(ClientSideStatementType.SHOW_PROTO_DESCRIPTORS_FILE_PATH)));
+ assertThat(subject.getResultSet(), is(notNullValue()));
+ assertThat(subject.getResultSet().next(), is(true));
+ assertThat(subject.getResultSet().getString("path"), is(equalTo("descriptors.pb")));
+ assertThat(subject.getResultSet().next(), is(false));
}
@Test
@@ -190,4 +206,19 @@ public void testTimestampResultSetGetResultSet() {
is(equalTo(Timestamp.ofTimeSecondsAndNanos(10L, 10))));
assertThat(subject.getResultSet().next(), is(false));
}
+
+ @Test
+ public void testBytesResultSetGetResultSet() {
+ StatementResult subject =
+ StatementResultImpl.resultSet(
+ "foo", "protoDescriptors".getBytes(), ClientSideStatementType.SHOW_PROTO_DESCRIPTORS);
+ assertEquals(subject.getResultType(), ResultType.RESULT_SET);
+ assertEquals(
+ subject.getClientSideStatementType(), ClientSideStatementType.SHOW_PROTO_DESCRIPTORS);
+ assertNotNull(subject.getResultSet());
+ assertTrue(subject.getResultSet().next());
+ assertEquals(
+ subject.getResultSet().getBytes("foo"), ByteArray.copyFrom("protoDescriptors".getBytes()));
+ assertFalse(subject.getResultSet().next());
+ }
}
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
index 4b6aec5b965..a098a275462 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql
@@ -23196,3 +23196,797 @@ set max_partitioned_parallelism = 10/-;
NEW_CONNECTION;
@EXPECT EXCEPTION INVALID_ARGUMENT
set max_partitioned_parallelism =/-10;
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+SET PROTO_DESCRIPTORS='PROTODESCRIPTORSBASE64';
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+ set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+ set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+
+
+
+set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64' ;
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64' ;
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64'
+
+;
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+set
+proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors='protodescriptorsbase64'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-proto_descriptors='protodescriptorsbase64';
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+SET PROTO_DESCRIPTORS_FILE_PATH='SRC/TEST/RESOURCES/COM/GOOGLE/CLOUD/SPANNER/DESCRIPTORS.PB';
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+ set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+ set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+
+
+
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb' ;
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb' ;
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'
+
+;
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+set
+proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb' bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set%proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set_proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set&proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set$proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set@proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set!proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set*proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set(proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb');
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set)proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set+proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-#proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set\proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set?proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set-/proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/#proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb'/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+set/-proto_descriptors_file_path='src/test/resources/com/google/cloud/spanner/descriptors.pb';
+NEW_CONNECTION;
+show variable proto_descriptors;
+NEW_CONNECTION;
+SHOW VARIABLE PROTO_DESCRIPTORS;
+NEW_CONNECTION;
+show variable proto_descriptors;
+NEW_CONNECTION;
+ show variable proto_descriptors;
+NEW_CONNECTION;
+ show variable proto_descriptors;
+NEW_CONNECTION;
+
+
+
+show variable proto_descriptors;
+NEW_CONNECTION;
+show variable proto_descriptors ;
+NEW_CONNECTION;
+show variable proto_descriptors ;
+NEW_CONNECTION;
+show variable proto_descriptors
+
+;
+NEW_CONNECTION;
+show variable proto_descriptors;
+NEW_CONNECTION;
+show variable proto_descriptors;
+NEW_CONNECTION;
+show
+variable
+proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable%proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable_proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable&proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable$proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable@proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable!proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable*proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable(proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors);
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable)proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable+proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-#proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable\proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable?proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-/proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/#proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-show variable proto_descriptors;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/-proto_descriptors;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+SHOW VARIABLE PROTO_DESCRIPTORS_FILE_PATH;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+ show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+ show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+
+
+
+show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path ;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path ;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path
+
+;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+show
+variable
+proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+foo show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path bar;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+%show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path%;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable%proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+_show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path_;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable_proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+&show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path&;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable&proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+$show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path$;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable$proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+@show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path@;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable@proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+!show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path!;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable!proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+*show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path*;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable*proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+(show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path(;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable(proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+)show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path);
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable)proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
++show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path+;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable+proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-#show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path-#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-#proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+\show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path\;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable\proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+?show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path?;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable?proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+-/show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path-/;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable-/proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/#show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path/#;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/#proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION INVALID_ARGUMENT
+/-show variable proto_descriptors_file_path;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable proto_descriptors_file_path/-;
+NEW_CONNECTION;
+@EXPECT EXCEPTION UNIMPLEMENTED
+show variable/-proto_descriptors_file_path;
diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITDdlTest.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITDdlTest.sql
index 2dea0423151..2efc59ed36e 100644
--- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITDdlTest.sql
+++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITDdlTest.sql
@@ -127,7 +127,7 @@ WHERE TABLE_NAME='VALID_MULTIPLE_DDL_IN_DDL_BATCH_1' OR TABLE_NAME='VALID_MULTIP
NEW_CONNECTION;
-/*
+/*
* Do a test that shows that a DDL batch might only execute some of the statements,
* for example if data in a table prevents a unique index from being created.
*/
@@ -187,3 +187,44 @@ RUN BATCH;
START BATCH DDL;
ABORT BATCH;
+
+NEW_CONNECTION;
+-- Set proto descriptors using relative path to the descriptors.pb file. This gets applied for next DDL statement
+SET PROTO_DESCRIPTORS_FILE_PATH = 'src/test/resources/com/google/cloud/spanner/descriptors.pb';
+-- Check if Proto descriptors is set
+@EXPECT RESULT_SET 'PROTO_DESCRIPTORS_FILE_PATH'
+SHOW VARIABLE PROTO_DESCRIPTORS_FILE_PATH;
+
+CREATE PROTO BUNDLE (examples.spanner.music.Genre);
+-- Check if Proto descriptors is reset to null
+@EXPECT RESULT_SET 'PROTO_DESCRIPTORS',null
+SHOW VARIABLE PROTO_DESCRIPTORS;
+@EXPECT RESULT_SET 'PROTO_DESCRIPTORS_FILE_PATH',null
+SHOW VARIABLE PROTO_DESCRIPTORS_FILE_PATH;
+
+-- Set Proto Descriptor as base64 string. This gets applied to all statements in next DDL batch
+SET PROTO_DESCRIPTORS = 'CvYCCgxzaW5nZXIucHJvdG8SFmV4YW1wbGVzLnNwYW5uZXIubXVzaWMi6gEKClNpbmdlckluZm8SIAoJc2luZ2VyX2lkGAEgASgDSABSCHNpbmdlcklkiAEBEiIKCmJpcnRoX2RhdGUYAiABKAlIAVIJYmlydGhEYXRliAEBEiUKC25hdGlvbmFsaXR5GAMgASgJSAJSC25hdGlvbmFsaXR5iAEBEjgKBWdlbnJlGAQgASgOMh0uZXhhbXBsZXMuc3Bhbm5lci5tdXNpYy5HZW5yZUgDUgVnZW5yZYgBAUIMCgpfc2luZ2VyX2lkQg0KC19iaXJ0aF9kYXRlQg4KDF9uYXRpb25hbGl0eUIICgZfZ2VucmUqLgoFR2VucmUSBwoDUE9QEAASCAoESkFaWhABEggKBEZPTEsQAhIICgRST0NLEANCKQoYY29tLmdvb2dsZS5jbG91ZC5zcGFubmVyQgtTaW5nZXJQcm90b1AAYgZwcm90bzM=';
+
+@EXPECT RESULT_SET 'PROTO_DESCRIPTORS'
+SHOW VARIABLE PROTO_DESCRIPTORS;
+
+START BATCH DDL;
+ALTER PROTO BUNDLE INSERT (examples.spanner.music.SingerInfo);
+CREATE TABLE Singers (
+ SingerId INT64 NOT NULL,
+ FirstName STRING(1024),
+ LastName STRING(1024),
+ SingerInfo examples.spanner.music.SingerInfo,
+ SingerGenre examples.spanner.music.Genre
+) PRIMARY KEY (SingerId);
+-- Run the batch
+RUN BATCH;
+
+-- Check if Proto descriptors is reset to null
+@EXPECT RESULT_SET 'PROTO_DESCRIPTORS',null
+SHOW VARIABLE PROTO_DESCRIPTORS;
+-- Check that the table is created
+@EXPECT RESULT_SET
+SELECT COUNT(*) AS ACTUAL, 1 AS EXPECTED
+FROM INFORMATION_SCHEMA.TABLES
+WHERE TABLE_NAME='Singers';