diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 6ede96a21..0c26eec01 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -61,7 +61,7 @@ javadoc) integration) mvn -B ${INTEGRATION_TEST_ARGS} \ -ntp \ - -Dtest=ITBigQueryTest \ + -Dtest=ITBigQueryTest,ITRemoteUDFTest \ -DtrimStackTrace=false \ -Dclirr.skip=true \ -Denforcer.skip=true \ @@ -72,7 +72,7 @@ integration) nightly-it) mvn -B ${INTEGRATION_TEST_ARGS} \ -ntp \ - -Dtest=ITNightlyBigQueryTest \ + -Dtest=ITNightlyBigQueryTest,ITRemoteUDFTest \ -DtrimStackTrace=false \ -Dclirr.skip=true \ -Denforcer.skip=true \ @@ -82,20 +82,20 @@ nightly-it) ;; graalvm) # Run Integration Tests with Native Image. Skip running nightly tests in presubmits. - mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITBigQueryTest -Pnative -Penable-integration-tests test + mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITBigQueryTest,ITRemoteUDFTest -Pnative -Penable-integration-tests test RETURN_CODE=$? ;; graalvm17) # Run Integration Tests with Native Image. Skip running nightly tests in presubmits. - mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITBigQueryTest -Pnative -Penable-integration-tests test + mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITBigQueryTest,ITRemoteUDFTest -Pnative -Penable-integration-tests test RETURN_CODE=$? ;; nightly-graalvm) - mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITNightlyBigQueryTest -Pnative -Penable-integration-tests test + mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITNightlyBigQueryTest,ITRemoteUDFTest -Pnative -Penable-integration-tests test RETURN_CODE=$? ;; nightly-graalvm17) - mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITNightlyBigQueryTest -Pnative -Penable-integration-tests test + mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Dtest=ITNightlyBigQueryTest,ITRemoteUDFTest -Pnative -Penable-integration-tests test RETURN_CODE=$? ;; samples) diff --git a/google-cloud-bigquery/clirr-ignored-differences.xml b/google-cloud-bigquery/clirr-ignored-differences.xml index 7a882777a..6b76f78eb 100644 --- a/google-cloud-bigquery/clirr-ignored-differences.xml +++ b/google-cloud-bigquery/clirr-ignored-differences.xml @@ -19,4 +19,9 @@ com/google/cloud/bigquery/ExternalTableDefinition* *ReferenceFileSchemaUri(*) + + 7013 + com/google/cloud/bigquery/RoutineInfo* + *RemoteFunctionOptions(*) + \ No newline at end of file diff --git a/google-cloud-bigquery/pom.xml b/google-cloud-bigquery/pom.xml index 8c1c8fa42..b3301e0df 100644 --- a/google-cloud-bigquery/pom.xml +++ b/google-cloud-bigquery/pom.xml @@ -131,9 +131,14 @@ google-cloud-datacatalog test + + com.google.cloud + google-cloud-bigqueryconnection + test + com.google.api.grpc - proto-google-cloud-datacatalog-v1 + proto-google-cloud-bigqueryconnection-v1 test diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RemoteFunctionOptions.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RemoteFunctionOptions.java new file mode 100644 index 000000000..88496dcb4 --- /dev/null +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RemoteFunctionOptions.java @@ -0,0 +1,139 @@ +/* + * Copyright 2022 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.bigquery; + +import com.google.auto.value.AutoValue; +import java.io.Serializable; +import java.util.Map; +import javax.annotation.Nullable; + +/** Represents Remote Function Options. Options for a remote user-defined function. */ +@AutoValue +public abstract class RemoteFunctionOptions implements Serializable { + + private static final long serialVersionUID = -7334249450657429792L; + + @AutoValue.Builder + public abstract static class Builder { + + /** + * Sets Endpoint argument Endpoint of the user-provided remote service, e.g. + * ```https://us-east1-my_gcf_project.cloudfunctions.net/remote_add``` + */ + public abstract Builder setEndpoint(String endpoint); + + /** + * Fully qualified name of the user-provided connection object which holds the authentication + * information to send requests to the remote service. Format: + * ```\"projects/{projectId}/locations/{locationId}/connections/{connectionId}\"``` + */ + public abstract Builder setConnection(String connection); + + /** + * User-defined context as a set of key/value pairs, which will be sent as function invocation + * context together with batched arguments in the requests to the remote service. The total + * number of bytes of keys and values must be less than 8KB. + */ + public abstract Builder setUserDefinedContext(Map userDefinedContext); + + /** + * Max number of rows in each batch sent to the remote service. If absent or if 0, BigQuery + * dynamically decides the number of rows in a batch. + */ + public abstract Builder setMaxBatchingRows(Long maxBatchingRows); + + /** Creates a {@code RemoteFunctionOptions} object. */ + public abstract RemoteFunctionOptions build(); + } + + /** + * Returns the endpoint of the user-provided service. + * + * @return String + */ + @Nullable + public abstract String getEndpoint(); + + /** + * Returns the fully qualified name of the user-provided connection object. + * + * @return String + */ + @Nullable + public abstract String getConnection(); + + /** + * Returns the user-defined context as a set of key/value pairs. + * + * @return Map + */ + @Nullable + public abstract Map getUserDefinedContext(); + + /** + * Returns max number of rows in each batch sent to the remote service. + * + * @return Long + */ + @Nullable + public abstract Long getMaxBatchingRows(); + + /** + * Returns a builder pre-populated using the current values of this {@code RemoteFunctionOptions}. + */ + public abstract RemoteFunctionOptions.Builder toBuilder(); + + /** Returns a builder for a {@Code RemoteFunctionOptions} object. */ + public static RemoteFunctionOptions.Builder newBuilder() { + return new AutoValue_RemoteFunctionOptions.Builder(); + } + + public com.google.api.services.bigquery.model.RemoteFunctionOptions toPb() { + com.google.api.services.bigquery.model.RemoteFunctionOptions remoteFunctionOptions = + new com.google.api.services.bigquery.model.RemoteFunctionOptions(); + if (getEndpoint() != null) { + remoteFunctionOptions.setEndpoint(getEndpoint()); + } + if (getConnection() != null) { + remoteFunctionOptions.setConnection(getConnection()); + } + if (getUserDefinedContext() != null) { + remoteFunctionOptions.setUserDefinedContext(getUserDefinedContext()); + } + if (getMaxBatchingRows() != null) { + remoteFunctionOptions.setMaxBatchingRows(getMaxBatchingRows()); + } + return remoteFunctionOptions; + } + + static RemoteFunctionOptions fromPb( + com.google.api.services.bigquery.model.RemoteFunctionOptions remoteFunctionOptionsPb) { + RemoteFunctionOptions.Builder builder = newBuilder(); + if (remoteFunctionOptionsPb.getEndpoint() != null) { + builder.setEndpoint(remoteFunctionOptionsPb.getEndpoint()); + } + if (remoteFunctionOptionsPb.getConnection() != null) { + builder.setConnection(remoteFunctionOptionsPb.getConnection()); + } + if (remoteFunctionOptionsPb.getUserDefinedContext() != null) { + builder.setUserDefinedContext(remoteFunctionOptionsPb.getUserDefinedContext()); + } + if (remoteFunctionOptionsPb.getMaxBatchingRows() != null) { + builder.setMaxBatchingRows(remoteFunctionOptionsPb.getMaxBatchingRows()); + } + return builder.build(); + } +} diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java index a5232c3f9..d2d6ec683 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java @@ -129,6 +129,12 @@ public Builder setBody(String body) { return this; } + @Override + public Builder setRemoteFunctionOptions(RemoteFunctionOptions remoteFunctionOptions) { + infoBuilder.setRemoteFunctionOptions(remoteFunctionOptions); + return this; + } + @Override public Routine build() { return new Routine(bigquery, infoBuilder); diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java index daa745577..3001d2fb9 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java @@ -70,6 +70,7 @@ public Routine apply(RoutineInfo routineInfo) { private final StandardSQLTableType returnTableType; private final List importedLibrariesList; private final String body; + private final RemoteFunctionOptions remoteFunctionOptions; public abstract static class Builder { @@ -148,6 +149,14 @@ public abstract static class Builder { */ public abstract Builder setBody(String body); + /** + * Optional. Remote function specific options. + * + * @param remoteFunctionOptions + * @return + */ + public abstract Builder setRemoteFunctionOptions(RemoteFunctionOptions remoteFunctionOptions); + /** Creates a {@code RoutineInfo} object. */ public abstract RoutineInfo build(); } @@ -166,6 +175,7 @@ static class BuilderImpl extends Builder { private StandardSQLTableType returnTableType; private List importedLibrariesList; private String body; + private RemoteFunctionOptions remoteFunctionOptions; BuilderImpl() {} @@ -183,6 +193,7 @@ static class BuilderImpl extends Builder { this.returnTableType = routineInfo.returnTableType; this.importedLibrariesList = routineInfo.importedLibrariesList; this.body = routineInfo.body; + this.remoteFunctionOptions = routineInfo.remoteFunctionOptions; } BuilderImpl(Routine routinePb) { @@ -210,6 +221,10 @@ static class BuilderImpl extends Builder { this.importedLibrariesList = routinePb.getImportedLibraries(); } this.body = routinePb.getDefinitionBody(); + if (routinePb.getRemoteFunctionOptions() != null) { + this.remoteFunctionOptions = + RemoteFunctionOptions.fromPb(routinePb.getRemoteFunctionOptions()); + } } @Override @@ -290,6 +305,12 @@ public Builder setBody(String body) { return this; } + @Override + public Builder setRemoteFunctionOptions(RemoteFunctionOptions remoteFunctionOptions) { + this.remoteFunctionOptions = remoteFunctionOptions; + return this; + } + @Override public RoutineInfo build() { return new RoutineInfo(this); @@ -310,6 +331,7 @@ public RoutineInfo build() { this.returnTableType = builder.returnTableType; this.importedLibrariesList = builder.importedLibrariesList; this.body = builder.body; + this.remoteFunctionOptions = builder.remoteFunctionOptions; } /** Returns the RoutineId identified for the routine resource. * */ @@ -384,6 +406,11 @@ public String getBody() { return body; } + /** Returns the Remote function specific options. */ + public RemoteFunctionOptions getRemoteFunctionOptions() { + return remoteFunctionOptions; + }; + /** Returns a builder pre-populated using the current values of this routine. */ public Builder toBuilder() { return new BuilderImpl(this); @@ -405,6 +432,7 @@ public String toString() { .add("returnTableType", returnTableType) .add("importedLibrariesList", importedLibrariesList) .add("body", body) + .add("remoteFunctionOptions", remoteFunctionOptions) .toString(); } @@ -423,7 +451,8 @@ public int hashCode() { returnType, returnTableType, importedLibrariesList, - body); + body, + remoteFunctionOptions); } @Override @@ -474,6 +503,9 @@ Routine toPb() { if (getReturnTableType() != null) { routinePb.setReturnTableType(getReturnTableType().toPb()); } + if (getRemoteFunctionOptions() != null) { + routinePb.setRemoteFunctionOptions(getRemoteFunctionOptions().toPb()); + } return routinePb; } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RemoteFunctionOptionsTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RemoteFunctionOptionsTest.java new file mode 100644 index 000000000..8ee0e4564 --- /dev/null +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RemoteFunctionOptionsTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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.bigquery; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class RemoteFunctionOptionsTest { + private static final String endpoint = "https://aaabbbccc-uc.a.run.app"; + private static final String connection = + "projects/{projectId}/locations/{locationId}/connections/{connectionId}"; + private static final Map userDefinedContext = + new HashMap() { + { + put("key1", "value1"); + put("key2", "value2"); + } + }; + private static final Long maxBatchingRows = 20L; + + private static final RemoteFunctionOptions REMOTE_FUNCTION_OPTIONS = + RemoteFunctionOptions.newBuilder() + .setEndpoint(endpoint) + .setConnection(connection) + .setUserDefinedContext(userDefinedContext) + .setMaxBatchingRows(maxBatchingRows) + .build(); + + @Test + public void testToBuilder() { + compareRemoteFunctionOptions( + REMOTE_FUNCTION_OPTIONS, REMOTE_FUNCTION_OPTIONS.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(endpoint, REMOTE_FUNCTION_OPTIONS.getEndpoint()); + assertEquals(connection, REMOTE_FUNCTION_OPTIONS.getConnection()); + assertEquals(userDefinedContext, REMOTE_FUNCTION_OPTIONS.getUserDefinedContext()); + assertEquals(maxBatchingRows, REMOTE_FUNCTION_OPTIONS.getMaxBatchingRows()); + } + + @Test + public void testToAndFromPb() { + compareRemoteFunctionOptions( + REMOTE_FUNCTION_OPTIONS, RemoteFunctionOptions.fromPb(REMOTE_FUNCTION_OPTIONS.toPb())); + } + + public void compareRemoteFunctionOptions( + RemoteFunctionOptions expected, RemoteFunctionOptions actual) { + assertEquals(expected, actual); + assertEquals(expected.getEndpoint(), actual.getEndpoint()); + assertEquals(expected.getConnection(), actual.getConnection()); + assertEquals(expected.getMaxBatchingRows(), actual.getMaxBatchingRows()); + assertEquals(expected.getUserDefinedContext(), actual.getUserDefinedContext()); + } +} diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java index 89bed602e..c9080e851 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java @@ -26,7 +26,9 @@ import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -74,6 +76,20 @@ public class RoutineTest { ImmutableList.of("gs://foo", "gs://bar", "gs://baz"); private static final String BODY = "body"; + private static final Map userDefinedContext = + new HashMap() { + { + put("key1", "value1"); + put("key2", "value2"); + } + }; + private static final RemoteFunctionOptions REMOTE_FUNCTION_OPTIONS = + RemoteFunctionOptions.newBuilder() + .setEndpoint("endpoint") + .setConnection("connection") + .setUserDefinedContext(userDefinedContext) + .setMaxBatchingRows(10L) + .build(); private static final RoutineInfo ROUTINE_INFO = RoutineInfo.newBuilder(ROUTINE_ID) @@ -87,6 +103,7 @@ public class RoutineTest { .setReturnType(RETURN_TYPE) .setImportedLibraries(IMPORTED_LIBRARIES) .setBody(BODY) + .setRemoteFunctionOptions(REMOTE_FUNCTION_OPTIONS) .build(); private static final RoutineInfo ROUTINE_INFO_TVF = @@ -128,6 +145,7 @@ public void testBuilder() { .setReturnType(RETURN_TYPE) .setImportedLibraries(IMPORTED_LIBRARIES) .setBody(BODY) + .setRemoteFunctionOptions(REMOTE_FUNCTION_OPTIONS) .build(); assertEquals(ETAG, builtRoutine.getEtag()); assertEquals(DETERMINISM_LEVEL, builtRoutine.getDeterminismLevel()); @@ -228,5 +246,6 @@ public void compareRoutineInfo(RoutineInfo expected, RoutineInfo value) { assertEquals(expected.getImportedLibraries(), value.getImportedLibraries()); assertEquals(expected.getBody(), value.getBody()); assertEquals(expected.hashCode(), value.hashCode()); + assertEquals(expected.getRemoteFunctionOptions(), value.getRemoteFunctionOptions()); } } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITRemoteUDFTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITRemoteUDFTest.java new file mode 100644 index 000000000..7e74a8f4c --- /dev/null +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITRemoteUDFTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2022 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.bigquery.it; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.RemoteFunctionOptions; +import com.google.cloud.bigquery.Routine; +import com.google.cloud.bigquery.RoutineArgument; +import com.google.cloud.bigquery.RoutineId; +import com.google.cloud.bigquery.RoutineInfo; +import com.google.cloud.bigquery.StandardSQLDataType; +import com.google.cloud.bigquery.connection.v1.CloudResourceProperties; +import com.google.cloud.bigquery.connection.v1.Connection; +import com.google.cloud.bigquery.connection.v1.CreateConnectionRequest; +import com.google.cloud.bigquery.connection.v1.DeleteConnectionRequest; +import com.google.cloud.bigquery.connection.v1.LocationName; +import com.google.cloud.bigquery.testing.RemoteBigQueryHelper; +import com.google.cloud.bigqueryconnection.v1.ConnectionServiceClient; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +public class ITRemoteUDFTest { + + private static final String ID = UUID.randomUUID().toString().substring(0, 8); + private static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); + private static final String CONNECTION_ID = "test-connectin-id-" + ID; + private static final String LOCATION = "US"; + private static final String PARENT = LocationName.of(PROJECT_ID, LOCATION).toString(); + private static final String REMOTE_ENDPOINT = "https://aaabbbccc-uc.a.run.app"; + private static final String ROUTINE_DATASET = RemoteBigQueryHelper.generateDatasetName(); + private static ConnectionServiceClient client; + private static Connection connection; + private static BigQuery bigquery; + + @Before + public void setUp() throws IOException { + RemoteBigQueryHelper bigqueryHelper = RemoteBigQueryHelper.create(); + bigquery = bigqueryHelper.getOptions().getService(); + client = ConnectionServiceClient.create(); + + DatasetInfo info = + DatasetInfo.newBuilder(ROUTINE_DATASET).setDescription("java routine lifecycle").build(); + bigquery.create(info); + CloudResourceProperties cloudResourceProperties = CloudResourceProperties.newBuilder().build(); + CreateConnectionRequest request = + CreateConnectionRequest.newBuilder() + .setParent(PARENT) + .setConnection( + Connection.newBuilder().setCloudResource(cloudResourceProperties).build()) + .setConnectionId(CONNECTION_ID) + .build(); + connection = client.createConnection(request); + } + + @AfterClass + public static void afterClass() { + if (bigquery != null) { + RemoteBigQueryHelper.forceDelete(bigquery, ROUTINE_DATASET); + } + // delete a connection + DeleteConnectionRequest request = + DeleteConnectionRequest.newBuilder().setName(connection.getName()).build(); + client.deleteConnection(request); + client.close(); + } + + @Test + public void testRoutineRemoteUDF() { + String routineName = RemoteBigQueryHelper.generateRoutineName(); + RoutineId routineId = RoutineId.of(ROUTINE_DATASET, routineName); + Map userDefinedContext = + new HashMap() { + { + put("key1", "value1"); + put("key2", "value2"); + } + }; + + RemoteFunctionOptions remoteFunctionOptions = + RemoteFunctionOptions.newBuilder() + .setEndpoint(REMOTE_ENDPOINT) + .setConnection(connection.getName()) + .setMaxBatchingRows(Long.valueOf(30)) + .setUserDefinedContext(userDefinedContext) + .build(); + RoutineInfo routineInfo = + RoutineInfo.newBuilder(routineId) + .setRoutineType("SCALAR_FUNCTION") + .setArguments( + ImmutableList.of( + RoutineArgument.newBuilder() + .setName("x") + .setDataType(StandardSQLDataType.newBuilder("INT64").build()) + .build())) + .setRemoteFunctionOptions(remoteFunctionOptions) + .setReturnType(StandardSQLDataType.newBuilder("INT64").build()) + .build(); + + Routine routine = bigquery.create(routineInfo); + assertNotNull(routine); + assertEquals(routine.getRoutineType(), "SCALAR_FUNCTION"); + assertEquals(REMOTE_ENDPOINT, routine.getRemoteFunctionOptions().getEndpoint()); + assertEquals(connection.getName(), routine.getRemoteFunctionOptions().getConnection()); + } +} diff --git a/pom.xml b/pom.xml index 8458ba872..4a00f4980 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,18 @@ 2.12.0 test + + com.google.cloud + google-cloud-bigqueryconnection + 2.4.0 + test + + + com.google.api.grpc + proto-google-cloud-bigqueryconnection-v1 + 2.4.0 + test +