Skip to content

Commit 7e7c814

Browse files
harshachintagauravpurohit06pjuhosgcf-owl-bot[bot]
authored
feat(spanner): add support for Proto Columns in Connection API (#3123)
* feat: Support for Proto Messages & Enums (#2155) * feat: Importing Proto Changes Commit will be reverted, once PROTO changes are available publicly. * feat: Proto Message Implementation * feat: Adding support for enum * feat: Code refactoring Adding default implementation for newly added methods ByteArray compatability changes for Proto Messages * docs: Adding Java docs for all the newly added methods. * test: Sample Proto & Generated classes for unit test * feat: Adding bytes/proto & int64/enum compatability Adding Additional check for ChecksumResultSet * test: Adding unit tests * test: Adding unit tests for ValueBinder.java * feat: refactoring to add support for getValue & other minor changes * feat: Minor refactoring 1. Adding docs and formatting the code. 2. Adding additional methods for enum and message which accepts descriptors. * feat: Adding bytes/message & int64/enum compatability in Value * refactor: Minor refactoring * feat: Adding Proto Array Implementation * test: Implementing unit tests for array of protos and enums * refactor: adding clirr ignores * feat: Adding support for enum as Primary Key * feat: Code Review Changes, minor refactoring and adding docs * feat: Addressing review comments -Modified Docs/Comments -Minor Refactoring * refactor: Using Column instead of column to avoid test failures * feat: Minor refactoring -code review comments -adding function docs * samples: Adding samples for updating & querying Proto messages & enums (#2211) * samples: Adding samples for updating & querying Proto messages & enums * style: linting * style: linting * docs: Adding function and class doc * test: Proto Column Integration tests (#2212) * test: Adding Integration tests for Proto Messages & Enums * test: Adding additional test for Parameterized Queries, Primary Keys & Invalid Wire type errors. * style: Formatting * style: Formatting * test: Updating instance and db name * test: Adding inter compatability check while writing data * Configured jitpack.yml to use OpenJDK 11 (#2218) Co-authored-by: Pavol Juhos <[email protected]> * feat: add support for Proto Columns DDL (#2277) * feat: add code changes and tests for Proto columns DDL support * feat: add auto generated code * feat: code changes and tests for Proto columns DDL support * feat: add descriptors file * feat: code refactoring * feat: Integration tests and code refactoring * feat: code refactoring * feat: unit tests and clirr differences * feat: lint changes * feat: code refactor * feat: code refactoring * feat: code refactoring * feat: code refactoring * feat: add java docs to new methods * feat: lint formatting * feat: lint formatting changes * feat: lint formatting * feat: lint formatting * feat: test exception cases * feat: code refactoring * feat: add java docs and refactoring * feat: add java docs * feat: java docs refactor * feat: remove overload method setProtoDescriptors that accepts file path as input to avoid unexpected issues * feat: remove updateDdl method overload to update proto descriptor * teat: update pom file to run tests on cloud-devel region temporarily to validate main branch update * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix: revert host changes in pom.xml file * feat: add support for Proto Columns to Connection API * feat: add client side statement support for proto descriptor * feat: update existing unit tests to include proto descriptorss argument * feat: code refactor for client side statements * feat: add unit tests * feat: code refactoring for file path client statement * test: add integration test for DDL * fix: comment refactoring * feat: move proto descriptor file read logic to ConnectionImpl file to fix autogenerated client side statement tests * feat: add autogenerated test for new proto columns client side statements * feat: add unit tests to verify proto descriptor set via filepath * feat: add review comments * feat: add client side statement to show proto descriptors file path * fix: remove proto descriptors file extension validation * feat: comment refactor * feat: address review comments * feat: update tests and revert autogenerated code * chore: revert autogenerated coee * chore: revert autogenerated code * chore: revert autogenerated code * chore: lint format * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: regenerate descriptors file * chore: update schema * chore: update base64 value for protodescriptor * chore: update copyright year --------- Co-authored-by: Gaurav Purohit <[email protected]> Co-authored-by: Pavol Juhos <[email protected]> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent ae93c57 commit 7e7c814

23 files changed

+1587
-49
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+26-2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@
337337
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
338338
<method>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)</method>
339339
</difference>
340+
<difference>
341+
<differenceType>7005</differenceType>
342+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
343+
<method>com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(java.lang.String, java.lang.Iterable, java.lang.String)</method>
344+
<to>com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(com.google.cloud.spanner.Database, java.lang.Iterable, java.lang.String)</to>
345+
</difference>
340346
<difference>
341347
<differenceType>7005</differenceType>
342348
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
@@ -387,6 +393,12 @@
387393
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
388394
<method>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)</method>
389395
</difference>
396+
<difference>
397+
<differenceType>7005</differenceType>
398+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
399+
<method>com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(java.lang.String, java.lang.Iterable, java.lang.String)</method>
400+
<to>com.google.api.gax.longrunning.OperationFuture updateDatabaseDdl(com.google.cloud.spanner.Database, java.lang.Iterable, java.lang.String)</to>
401+
</difference>
390402
<difference>
391403
<differenceType>7005</differenceType>
392404
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
@@ -656,7 +668,7 @@
656668
<className>com/google/cloud/spanner/connection/Connection</className>
657669
<method>com.google.cloud.spanner.Spanner getSpanner()</method>
658670
</difference>
659-
671+
660672
<!-- Add DdlInTransactionMode -->
661673
<difference>
662674
<differenceType>7012</differenceType>
@@ -668,7 +680,7 @@
668680
<className>com/google/cloud/spanner/connection/Connection</className>
669681
<method>com.google.cloud.spanner.connection.DdlInTransactionMode getDdlInTransactionMode()</method>
670682
</difference>
671-
683+
672684
<!-- Added extended tracing option -->
673685
<difference>
674686
<differenceType>7012</differenceType>
@@ -688,4 +700,16 @@
688700
<method>void setExcludeTxnFromChangeStreams(boolean)</method>
689701
</difference>
690702

703+
<!-- Added Proto descriptors for proto columns -->
704+
<difference>
705+
<differenceType>7012</differenceType>
706+
<className>com/google/cloud/spanner/connection/Connection</className>
707+
<method>byte[] getProtoDescriptors()</method>
708+
</difference>
709+
<difference>
710+
<differenceType>7012</differenceType>
711+
<className>com/google/cloud/spanner/connection/Connection</className>
712+
<method>void setProtoDescriptors(byte[])</method>
713+
</difference>
714+
691715
</differences>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public static Type pgOid() {
147147
/**
148148
* To get the descriptor for the {@code PROTO} type.
149149
*
150-
* @param protoTypeFqn Proto fully qualified name (ex: "spanner.examples.music.SingerInfo").
150+
* @param protoTypeFqn Proto fully qualified name (ex: "examples.spanner.music.SingerInfo").
151151
*/
152152
public static Type proto(String protoTypeFqn) {
153153
return new Type(Code.PROTO, protoTypeFqn);
@@ -156,7 +156,7 @@ public static Type proto(String protoTypeFqn) {
156156
/**
157157
* To get the descriptor for the {@code ENUM} type.
158158
*
159-
* @param protoTypeFqn Proto ENUM fully qualified name (ex: "spanner.examples.music.Genre")
159+
* @param protoTypeFqn Proto ENUM fully qualified name (ex: "examples.spanner.music.Genre")
160160
*/
161161
public static Type protoEnum(String protoTypeFqn) {
162162
return new Type(Code.ENUM, protoTypeFqn);

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java

+44
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel;
2727
import com.google.common.base.Function;
2828
import com.google.common.base.Preconditions;
29+
import com.google.common.base.Strings;
2930
import com.google.protobuf.Duration;
3031
import com.google.protobuf.util.Durations;
3132
import com.google.spanner.v1.DirectedReadOptions;
3233
import com.google.spanner.v1.RequestOptions.Priority;
34+
import java.util.Base64;
3335
import java.util.EnumSet;
3436
import java.util.HashMap;
3537
import java.util.Locale;
@@ -563,4 +565,46 @@ public String convert(String value) {
563565
return value.substring(7).trim();
564566
}
565567
}
568+
569+
/** Converter for converting Base64 encoded string to byte[] */
570+
static class ProtoDescriptorsConverter implements ClientSideStatementValueConverter<byte[]> {
571+
572+
public ProtoDescriptorsConverter(String allowedValues) {}
573+
574+
@Override
575+
public Class<byte[]> getParameterClass() {
576+
return byte[].class;
577+
}
578+
579+
@Override
580+
public byte[] convert(String value) {
581+
if (value == null || value.length() == 0 || value.equalsIgnoreCase("null")) {
582+
return null;
583+
}
584+
try {
585+
return Base64.getDecoder().decode(value);
586+
} catch (IllegalArgumentException e) {
587+
return null;
588+
}
589+
}
590+
}
591+
592+
/** Converter for converting String that take in file path as input to String */
593+
static class ProtoDescriptorsFileConverter implements ClientSideStatementValueConverter<String> {
594+
595+
public ProtoDescriptorsFileConverter(String allowedValues) {}
596+
597+
@Override
598+
public Class<String> getParameterClass() {
599+
return String.class;
600+
}
601+
602+
@Override
603+
public String convert(String filePath) {
604+
if (Strings.isNullOrEmpty(filePath)) {
605+
return null;
606+
}
607+
return filePath;
608+
}
609+
}
566610
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java

+20
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.Set;
4848
import java.util.concurrent.ExecutionException;
4949
import java.util.concurrent.TimeUnit;
50+
import javax.annotation.Nonnull;
5051

5152
/**
5253
* Internal connection API for Google Cloud Spanner. This interface may introduce breaking changes
@@ -403,6 +404,25 @@ default boolean isExcludeTxnFromChangeStreams() {
403404
throw new UnsupportedOperationException();
404405
}
405406

407+
/**
408+
* Sets the proto descriptors to use for the next DDL statement (single or batch) that will be
409+
* executed. The proto descriptor is automatically cleared after the statement is executed.
410+
*
411+
* @param protoDescriptors The proto descriptors to use with the next DDL statement (single or
412+
* batch) that will be executed on this connection.
413+
*/
414+
default void setProtoDescriptors(@Nonnull byte[] protoDescriptors) {
415+
throw new UnsupportedOperationException();
416+
}
417+
418+
/**
419+
* @return The proto descriptor that will be used with the next DDL statement (single or batch)
420+
* that is executed on this connection.
421+
*/
422+
default byte[] getProtoDescriptors() {
423+
throw new UnsupportedOperationException();
424+
}
425+
406426
/**
407427
* @return <code>true</code> if this connection will automatically retry read/write transactions
408428
* that abort. This method may only be called when the connection is in read/write

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

+67-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.api.core.ApiFuture;
2323
import com.google.api.core.ApiFutures;
2424
import com.google.api.gax.core.GaxProperties;
25+
import com.google.cloud.ByteArray;
2526
import com.google.cloud.Timestamp;
2627
import com.google.cloud.spanner.AsyncResultSet;
2728
import com.google.cloud.spanner.BatchClient;
@@ -65,6 +66,9 @@
6566
import io.opentelemetry.api.common.AttributesBuilder;
6667
import io.opentelemetry.api.trace.Span;
6768
import io.opentelemetry.api.trace.Tracer;
69+
import java.io.File;
70+
import java.io.FileInputStream;
71+
import java.io.InputStream;
6872
import java.time.Duration;
6973
import java.util.ArrayList;
7074
import java.util.Arrays;
@@ -81,6 +85,7 @@
8185
import java.util.concurrent.TimeUnit;
8286
import java.util.concurrent.TimeoutException;
8387
import java.util.stream.Collectors;
88+
import javax.annotation.Nonnull;
8489
import javax.annotation.Nullable;
8590
import org.threeten.bp.Instant;
8691

@@ -113,7 +118,7 @@ private LeakedConnectionException() {
113118
}
114119
}
115120

116-
private volatile LeakedConnectionException leakedException;;
121+
private volatile LeakedConnectionException leakedException;
117122
private final SpannerPool spannerPool;
118123
private AbstractStatementParser statementParser;
119124
/**
@@ -268,10 +273,11 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
268273

269274
private String transactionTag;
270275
private String statementTag;
271-
272276
private boolean excludeTxnFromChangeStreams;
273277

274278
private Duration maxCommitDelay;
279+
private byte[] protoDescriptors;
280+
private String protoDescriptorsFilePath;
275281

276282
/** Create a connection and register it in the SpannerPool. */
277283
ConnectionImpl(ConnectionOptions options) {
@@ -353,6 +359,7 @@ public Spanner getSpanner() {
353359
private DdlClient createDdlClient() {
354360
return DdlClient.newBuilder()
355361
.setDatabaseAdminClient(spanner.getDatabaseAdminClient())
362+
.setProjectId(options.getProjectId())
356363
.setInstanceId(options.getInstanceId())
357364
.setDatabaseName(options.getDatabaseName())
358365
.build();
@@ -763,6 +770,52 @@ public void setExcludeTxnFromChangeStreams(boolean excludeTxnFromChangeStreams)
763770
this.excludeTxnFromChangeStreams = excludeTxnFromChangeStreams;
764771
}
765772

773+
@Override
774+
public byte[] getProtoDescriptors() {
775+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
776+
if (this.protoDescriptors == null && this.protoDescriptorsFilePath != null) {
777+
// Read from file if filepath is valid
778+
try {
779+
File protoDescriptorsFile = new File(this.protoDescriptorsFilePath);
780+
if (!protoDescriptorsFile.isFile()) {
781+
throw SpannerExceptionFactory.newSpannerException(
782+
ErrorCode.INVALID_ARGUMENT,
783+
String.format(
784+
"File %s is not a valid proto descriptors file", this.protoDescriptorsFilePath));
785+
}
786+
InputStream pdStream = new FileInputStream(protoDescriptorsFile);
787+
this.protoDescriptors = ByteArray.copyFrom(pdStream).toByteArray();
788+
} catch (Exception exception) {
789+
throw SpannerExceptionFactory.newSpannerException(exception);
790+
}
791+
}
792+
return this.protoDescriptors;
793+
}
794+
795+
@Override
796+
public void setProtoDescriptors(@Nonnull byte[] protoDescriptors) {
797+
Preconditions.checkNotNull(protoDescriptors);
798+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
799+
ConnectionPreconditions.checkState(
800+
!isBatchActive(), "Proto descriptors cannot be set when a batch is active");
801+
this.protoDescriptors = protoDescriptors;
802+
this.protoDescriptorsFilePath = null;
803+
}
804+
805+
void setProtoDescriptorsFilePath(@Nonnull String protoDescriptorsFilePath) {
806+
Preconditions.checkNotNull(protoDescriptorsFilePath);
807+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
808+
ConnectionPreconditions.checkState(
809+
!isBatchActive(), "Proto descriptors file path cannot be set when a batch is active");
810+
this.protoDescriptorsFilePath = protoDescriptorsFilePath;
811+
this.protoDescriptors = null;
812+
}
813+
814+
String getProtoDescriptorsFilePath() {
815+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
816+
return this.protoDescriptorsFilePath;
817+
}
818+
766819
/**
767820
* Throws an {@link SpannerException} with code {@link ErrorCode#FAILED_PRECONDITION} if the
768821
* current state of this connection does not allow changing the setting for retryAbortsInternally.
@@ -1806,6 +1859,7 @@ UnitOfWork createNewUnitOfWork(
18061859
.setSpan(
18071860
createSpanForUnitOfWork(
18081861
statementType == StatementType.DDL ? DDL_STATEMENT : SINGLE_USE_TRANSACTION))
1862+
.setProtoDescriptors(getProtoDescriptors())
18091863
.build();
18101864
if (!isInternalMetadataQuery && !forceSingleUse) {
18111865
// Reset the transaction options after starting a single-use transaction.
@@ -1862,6 +1916,7 @@ UnitOfWork createNewUnitOfWork(
18621916
.setStatementTimeout(statementTimeout)
18631917
.withStatementExecutor(statementExecutor)
18641918
.setSpan(createSpanForUnitOfWork(DDL_BATCH))
1919+
.setProtoDescriptors(getProtoDescriptors())
18651920
.build();
18661921
default:
18671922
}
@@ -1885,7 +1940,11 @@ private void popUnitOfWorkFromTransactionStack() {
18851940
}
18861941

18871942
private ApiFuture<Void> executeDdlAsync(CallType callType, ParsedStatement ddl) {
1888-
return getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
1943+
ApiFuture<Void> result = getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
1944+
// reset proto descriptors after executing a DDL statement
1945+
this.protoDescriptors = null;
1946+
this.protoDescriptorsFilePath = null;
1947+
return result;
18891948
}
18901949

18911950
@Override
@@ -1985,6 +2044,11 @@ public ApiFuture<long[]> runBatchAsync() {
19852044
}
19862045
return ApiFutures.immediateFuture(new long[0]);
19872046
} finally {
2047+
if (isDdlBatchActive()) {
2048+
// reset proto descriptors after executing a DDL batch
2049+
this.protoDescriptors = null;
2050+
this.protoDescriptorsFilePath = null;
2051+
}
19882052
this.batchMode = BatchMode.NONE;
19892053
setDefaultTransactionOptions();
19902054
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java

+8
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ StatementResult statementSetPgSessionCharacteristicsTransactionMode(
138138

139139
StatementResult statementShowTransactionIsolationLevel();
140140

141+
StatementResult statementSetProtoDescriptors(byte[] protoDescriptors);
142+
143+
StatementResult statementSetProtoDescriptorsFilePath(String filePath);
144+
145+
StatementResult statementShowProtoDescriptors();
146+
147+
StatementResult statementShowProtoDescriptorsFilePath();
148+
141149
StatementResult statementExplain(String sql);
142150

143151
StatementResult statementShowDataBoostEnabled();

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java

+34
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
3636
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_STATISTICS_PACKAGE;
3737
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_VERSION;
38+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_PROTO_DESCRIPTORS;
39+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_PROTO_DESCRIPTORS_FILE_PATH;
3840
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READONLY;
3941
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READ_ONLY_STALENESS;
4042
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_RETRY_ABORTS_INTERNALLY;
@@ -59,6 +61,8 @@
5961
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
6062
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_STATISTICS_PACKAGE;
6163
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_VERSION;
64+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_PROTO_DESCRIPTORS;
65+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_PROTO_DESCRIPTORS_FILE_PATH;
6266
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READONLY;
6367
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READ_ONLY_STALENESS;
6468
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READ_TIMESTAMP;
@@ -637,6 +641,36 @@ public StatementResult statementRunPartitionedQuery(Statement statement) {
637641
ClientSideStatementType.RUN_PARTITIONED_QUERY);
638642
}
639643

644+
@Override
645+
public StatementResult statementSetProtoDescriptors(byte[] protoDescriptors) {
646+
Preconditions.checkNotNull(protoDescriptors);
647+
getConnection().setProtoDescriptors(protoDescriptors);
648+
return noResult(SET_PROTO_DESCRIPTORS);
649+
}
650+
651+
@Override
652+
public StatementResult statementSetProtoDescriptorsFilePath(String filePath) {
653+
Preconditions.checkNotNull(filePath);
654+
getConnection().setProtoDescriptorsFilePath(filePath);
655+
return noResult(SET_PROTO_DESCRIPTORS_FILE_PATH);
656+
}
657+
658+
@Override
659+
public StatementResult statementShowProtoDescriptors() {
660+
return resultSet(
661+
String.format("%sPROTO_DESCRIPTORS", getNamespace(connection.getDialect())),
662+
getConnection().getProtoDescriptors(),
663+
SHOW_PROTO_DESCRIPTORS);
664+
}
665+
666+
@Override
667+
public StatementResult statementShowProtoDescriptorsFilePath() {
668+
return resultSet(
669+
String.format("%sPROTO_DESCRIPTORS_FILE_PATH", getNamespace(connection.getDialect())),
670+
getConnection().getProtoDescriptorsFilePath(),
671+
SHOW_PROTO_DESCRIPTORS_FILE_PATH);
672+
}
673+
640674
private String processQueryPlan(PlanNode planNode) {
641675
StringBuilder planNodeDescription = new StringBuilder(" : { ");
642676
com.google.protobuf.Struct metadata = planNode.getMetadata();

0 commit comments

Comments
 (0)