Skip to content

feat(spanner): add support for Proto Columns in Connection API #3123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cbabd6c
feat: Support for Proto Messages & Enums (#2155)
gauravpurohit06 Dec 28, 2022
1fb6e94
samples: Adding samples for updating & querying Proto messages & enum…
gauravpurohit06 Jan 5, 2023
6c97b99
test: Proto Column Integration tests (#2212)
gauravpurohit06 Jan 5, 2023
dd358c2
Merge remote-tracking branch 'upstream/main' into proto-column-enhanc…
gauravpurohit06 Jan 5, 2023
53397e6
Configured jitpack.yml to use OpenJDK 11 (#2218)
gauravpurohit06 Jan 5, 2023
2f8eb42
feat: add support for Proto Columns DDL (#2277)
harshachinta Mar 15, 2023
a70f259
Merge branch 'main' into proto-column-enhancement-alpha
harshachinta Jun 8, 2023
9513753
teat: update pom file to run tests on cloud-devel region temporarily …
harshachinta Jun 8, 2023
6701f52
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jun 8, 2023
f63da18
fix: revert host changes in pom.xml file
harshachinta Jun 9, 2023
10ee620
feat: add support for Proto Columns to Connection API
harshachinta Jun 9, 2023
7feaf10
feat: add client side statement support for proto descriptor
harshachinta Jun 9, 2023
02a31b6
feat: update existing unit tests to include proto descriptorss argument
harshachinta Jun 10, 2023
7746e08
feat: code refactor for client side statements
harshachinta Jun 12, 2023
dcbe166
feat: add unit tests
harshachinta Jun 12, 2023
76fdd77
feat: code refactoring for file path client statement
harshachinta Jun 12, 2023
33044c4
test: add integration test for DDL
harshachinta Jun 12, 2023
ad0873a
fix: comment refactoring
harshachinta Jun 13, 2023
a5f1697
feat: move proto descriptor file read logic to ConnectionImpl file to…
harshachinta Jun 13, 2023
e8fed61
feat: add autogenerated test for new proto columns client side statem…
harshachinta Jun 13, 2023
b15dd75
feat: add unit tests to verify proto descriptor set via filepath
harshachinta Jun 14, 2023
0c9f4f6
feat: add review comments
harshachinta Jun 15, 2023
f97b249
feat: add client side statement to show proto descriptors file path
harshachinta Jun 15, 2023
a42761b
fix: remove proto descriptors file extension validation
harshachinta Jun 15, 2023
865fc0c
feat: comment refactor
harshachinta Jun 15, 2023
f1e3f3a
feat: address review comments
harshachinta Jun 23, 2023
ef6e043
Merge branch 'main' into jdbc-proto-column-feature
harshachinta May 23, 2024
3481cfe
feat: update tests and revert autogenerated code
harshachinta May 23, 2024
8468d9a
chore: revert autogenerated coee
harshachinta May 23, 2024
5e0bed1
chore: revert autogenerated code
harshachinta May 23, 2024
0e4361f
chore: revert autogenerated code
harshachinta May 23, 2024
1f0d006
chore: lint format
harshachinta May 23, 2024
771178a
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] May 23, 2024
f7b5cf6
chore: regenerate descriptors file
harshachinta May 23, 2024
b98b56e
chore: update schema
harshachinta May 23, 2024
85bb7f3
chore: update base64 value for protodescriptor
harshachinta May 24, 2024
94eb11c
chore: update copyright year
harshachinta May 24, 2024
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
Prev Previous commit
Next Next commit
feat: add support for Proto Columns to Connection API
  • Loading branch information
harshachinta committed Jun 9, 2023
commit 10ee6202652dd09d14359f6c10f5ff1c26e17dd2
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner.connection;

import com.google.cloud.ByteArray;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Options.RpcPriority;
import com.google.cloud.spanner.SpannerExceptionFactory;
Expand All @@ -28,6 +29,11 @@
import com.google.protobuf.Duration;
import com.google.protobuf.util.Durations;
import com.google.spanner.v1.RequestOptions.Priority;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
Expand Down Expand Up @@ -494,4 +500,52 @@ public String convert(String value) {
return value.substring(7).trim();
}
}

/** Converter for converting strings 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) {
if (value.length() == 0 || value.equalsIgnoreCase("null")) {
return null;
}
return Base64.getDecoder().decode(value);
}
}

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

public ProtoDescriptorsFileConverter(String allowedValues) {}

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

@Override
public byte[] convert(String filePath) {
if (filePath != null && filePath.length() > 0) {
try {
File protoDescriptorsFile = new File(filePath);
if (!protoDescriptorsFile.isFile()) {
throw new IOException("File does not exist.");
}
InputStream pdStream = new FileInputStream(protoDescriptorsFile);
return ByteArray.copyFrom(pdStream).toByteArray();
} catch (Exception e) {
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT, e.getMessage());
}
}
return null;
}
}
}
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)
* 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 @@ -63,6 +63,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 +86,7 @@ private LeakedConnectionException() {
}
}

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

private byte[] protoDescriptors;

/** Create a connection and register it in the SpannerPool. */
ConnectionImpl(ConnectionOptions options) {
Preconditions.checkNotNull(options);
Expand Down Expand Up @@ -278,6 +281,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 +627,21 @@ public void setStatementTag(String tag) {
this.statementTag = tag;
}

@Override
public byte[] getProtoDescriptors() {
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
return 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;
}

/**
* 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 +1398,7 @@ UnitOfWork createNewUnitOfWork() {
.setReturnCommitStats(returnCommitStats)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.setProtoDescriptors(protoDescriptors)
.build();
} else {
switch (getUnitOfWorkType()) {
Expand Down Expand Up @@ -1421,6 +1441,7 @@ UnitOfWork createNewUnitOfWork() {
.setDatabaseClient(dbClient)
.setStatementTimeout(statementTimeout)
.withStatementExecutor(statementExecutor)
.setProtoDescriptors(protoDescriptors)
.build();
default:
}
Expand All @@ -1444,7 +1465,11 @@ 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;
return result;
}

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

StatementResult statementShowTransactionIsolationLevel();

StatementResult statementSetProtoDescriptors(byte[] protoDescriptors);

StatementResult statementShowProtoDescriptors();

StatementResult statementExplain(String sql);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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_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 +46,7 @@
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_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 +495,20 @@ public StatementResult statementShowTransactionIsolationLevel() {
return resultSet("transaction_isolation", "serializable", SHOW_TRANSACTION_ISOLATION_LEVEL);
}

@Override
public StatementResult statementSetProtoDescriptors(byte[] protoDescriptors) {
getConnection().setProtoDescriptors(protoDescriptors);
return noResult(SET_PROTO_DESCRIPTORS);
}

@Override
public StatementResult statementShowProtoDescriptors() {
return resultSet(
String.format("%sPROTO_DESCRIPTORS", getNamespace(connection.getDialect())),
getConnection().getProtoDescriptors(),
SHOW_PROTO_DESCRIPTORS);
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Instead of adding this field, could we change the individual fields projectId, instanceId, databaseName into one field of type DatabaseId?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, will do this along with the previous comment as a seperate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comment will be addressed in PR #3126

private String instanceId;
private String databaseName;

Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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;
}
Expand All @@ -92,17 +104,24 @@ OperationFuture<Database, CreateDatabaseMetadata> executeCreateDatabase(
}

/** Execute a single DDL statement. */
OperationFuture<Void, UpdateDatabaseDdlMetadata> executeDdl(String ddl) {
return executeDdl(Collections.singletonList(ddl));
OperationFuture<Void, UpdateDatabaseDdlMetadata> executeDdl(String ddl, byte[] protoDescriptors) {
return executeDdl(Collections.singletonList(ddl), protoDescriptors);
}

/** Execute a list of DDL statements as one operation. */
OperationFuture<Void, UpdateDatabaseDdlMetadata> executeDdl(List<String> statements) {
OperationFuture<Void, UpdateDatabaseDdlMetadata> executeDdl(
List<String> 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. */
Expand Down
Loading