Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
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.RequestOptions.Priority;
import java.util.Base64;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
Expand Down Expand Up @@ -494,4 +496,46 @@ public String convert(String value) {
return value.substring(7).trim();
}
}

/** Converter for converting Base64 encoded string to byte[] */
static class ProtoDescriptorsConverter implements ClientSideStatementValueConverter<byte[]> {

public ProtoDescriptorsConverter(String allowedValues) {}

@Override
public Class<byte[]> getParameterClass() {
return byte[].class;
}

@Override
public byte[] convert(String value) {

@rajatbhatta rajatbhatta Jun 15, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add a NonNull annotation to the argument here. Similarly in other places wherever a null value is not expected.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These convert methods are not used by customers and is getting called from link. We generally use this annotation to warn users when they pass null. But here it is used in code internally and as users don't use this function, I guess it is not needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is being called from the same package and not meant to be public to customers. Then, should we make the methods package-protected?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods cannot be package-private, because they implement a method from an interface. The class itself is package-private, which protects the class and all its methods from being used by 'outside' users.

if (value == null || value.length() == 0 || value.equalsIgnoreCase("null")) {
return null;
}
try {
return Base64.getDecoder().decode(value);
} catch (IllegalArgumentException e) {
return null;
Comment thread
harshachinta marked this conversation as resolved.
}
}
}

/** Converter for converting String that take in file path as input to String */
static class ProtoDescriptorsFileConverter implements ClientSideStatementValueConverter<String> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We supporting reading from the file here? If Yes where should file be located just on resources or local path ?
We had similar discussion on #2277 (comment).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we are supporting reading from a file path given through a client side statement. This is needed to support ORM's and other frameworks. This discussion is available in the design doc.
Just a small brief, in PR#2277, we were treating file as a resource which brings in complications as they rely on class path. However, in this method we instead used File system to read from relative or absolute path.


public ProtoDescriptorsFileConverter(String allowedValues) {}

@Override
public Class<String> getParameterClass() {
return String.class;
}

@Override
public String convert(String filePath) {
if (Strings.isNullOrEmpty(filePath)) {
return null;
}
return filePath;
Comment thread
harshachinta marked this conversation as resolved.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.Iterator;
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
Expand Down Expand Up @@ -382,6 +383,25 @@ default String getStatementTag() {
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return The proto descriptor that will be used with the next DDL statement (single or batch)
* @return The proto descriptor that will be used only with the next DDL statement (single or batch)

* that is executed on this connection.
*/
default byte[] getProtoDescriptors() {
throw new UnsupportedOperationException();
}

/**
* @return <code>true</code> if this connection will automatically retry read/write transactions
* that abort. This method may only be called when the connection is in read/write
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.cloud.ByteArray;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.AsyncResultSet;
import com.google.cloud.spanner.CommitResponse;
Expand Down Expand Up @@ -51,6 +52,9 @@
import com.google.common.util.concurrent.MoreExecutors;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.ResultSetStats;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -63,6 +67,7 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nonnull;
import org.threeten.bp.Instant;

/** Implementation for {@link Connection}, the generic Spanner connection API (not JDBC). */
Expand All @@ -85,7 +90,7 @@ private LeakedConnectionException() {
}
}

private volatile LeakedConnectionException leakedException;;
private volatile LeakedConnectionException leakedException;
private final SpannerPool spannerPool;
private AbstractStatementParser statementParser;
/**
Expand Down Expand Up @@ -221,6 +226,9 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
private String transactionTag;
private String statementTag;

private byte[] protoDescriptors;
private String protoDescriptorsFilePath;

/** Create a connection and register it in the SpannerPool. */
ConnectionImpl(ConnectionOptions options) {
Preconditions.checkNotNull(options);
Expand Down Expand Up @@ -278,6 +286,7 @@ Spanner getSpanner() {
private DdlClient createDdlClient() {
return DdlClient.newBuilder()
.setDatabaseAdminClient(spanner.getDatabaseAdminClient())
.setProjectId(options.getProjectId())
.setInstanceId(options.getInstanceId())
.setDatabaseName(options.getDatabaseName())
.build();
Expand Down Expand Up @@ -623,6 +632,52 @@ public void setStatementTag(String tag) {
this.statementTag = tag;
}

@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()) {
Comment thread
harshachinta marked this conversation as resolved.
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;
Comment thread
harshachinta marked this conversation as resolved.
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.
Expand Down Expand Up @@ -1379,6 +1434,7 @@ UnitOfWork createNewUnitOfWork() {
.setReturnCommitStats(returnCommitStats)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.setProtoDescriptors(getProtoDescriptors())
.build();
} else {
switch (getUnitOfWorkType()) {
Expand Down Expand Up @@ -1421,6 +1477,7 @@ UnitOfWork createNewUnitOfWork() {
.setDatabaseClient(dbClient)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.setProtoDescriptors(getProtoDescriptors())
.build();
default:
}
Expand All @@ -1444,7 +1501,12 @@ private void popUnitOfWorkFromTransactionStack() {
}

private ApiFuture<Void> executeDdlAsync(CallType callType, ParsedStatement ddl) {
return getCurrentUnitOfWorkOrStartNewUnitOfWork().executeDdlAsync(callType, ddl);
ApiFuture<Void> result =
getCurrentUnitOfWorkOrStartNewUnitOfWork().executeDdlAsync(callType, ddl);
// reset proto descriptors after executing a DDL statement
this.protoDescriptors = null;
Comment thread
harshachinta marked this conversation as resolved.
this.protoDescriptorsFilePath = null;
return result;
}

@Override
Expand Down Expand Up @@ -1535,6 +1597,11 @@ public ApiFuture<long[]> runBatchAsync() {
}
return ApiFutures.immediateFuture(new long[0]);
} finally {
if (isDdlBatchActive()) {
// reset proto descriptors after executing a DDL batch
this.protoDescriptors = null;
Comment thread
harshachinta marked this conversation as resolved.
this.protoDescriptorsFilePath = null;
}
this.batchMode = BatchMode.NONE;
setDefaultTransactionOptions();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,13 @@ StatementResult statementSetPgSessionCharacteristicsTransactionMode(

StatementResult statementShowTransactionIsolationLevel();

StatementResult statementSetProtoDescriptors(byte[] protoDescriptors);

StatementResult statementSetProtoDescriptorsFilePath(String filePath);

StatementResult statementShowProtoDescriptors();

StatementResult statementShowProtoDescriptorsFilePath();

StatementResult statementExplain(String sql);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
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;
Expand All @@ -45,6 +47,8 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
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;
Expand Down Expand Up @@ -493,6 +497,36 @@ public StatementResult statementShowTransactionIsolationLevel() {
return resultSet("transaction_isolation", "serializable", SHOW_TRANSACTION_ISOLATION_LEVEL);
}

@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() {
Comment thread
harshachinta marked this conversation as resolved.
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ class DdlBatch extends AbstractBaseUnitOfWork {
private final DatabaseClient dbClient;
private final List<String> statements = new ArrayList<>();
private UnitOfWorkState state = UnitOfWorkState.STARTED;
private final byte[] protoDescriptors;

static class Builder extends AbstractBaseUnitOfWork.Builder<Builder, DdlBatch> {
private DdlClient ddlClient;
private DatabaseClient dbClient;
private byte[] protoDescriptors;

private Builder() {}

Expand All @@ -77,6 +79,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");
Expand All @@ -93,6 +100,7 @@ private DdlBatch(Builder builder) {
super(builder);
this.ddlClient = builder.ddlClient;
this.dbClient = builder.dbClient;
this.protoDescriptors = builder.protoDescriptors;
}

@Override
Expand Down Expand Up @@ -239,7 +247,7 @@ public ApiFuture<long[]> runBatchAsync(CallType callType) {
() -> {
try {
OperationFuture<Void, UpdateDatabaseDdlMetadata> operation =
ddlClient.executeDdl(statements);
ddlClient.executeDdl(statements, protoDescriptors);
try {
// Wait until the operation has finished.
getWithStatementTimeout(operation, RUN_BATCH_STATEMENT);
Expand Down
Loading