diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 1c8355fda..185f0ef11 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d835c34a7026e284170c41a0a66c956e03f247d0 # v2.27.7 + uses: github/codeql-action/upload-sarif@673cceb2b4886e2dfff697ab64a1ecd1c0a14a05 # v2.28.0 with: sarif_file: results.sarif diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index f971b05c9..5ced7ae24 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -17,7 +17,7 @@ jobs: # repository .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.41.0 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.41.1 with: # java-bigquery does not produce a BOM. Fortunately the root pom.xml # defines google-cloud-bigquery in dependencyManagement section. So diff --git a/.kokoro/continuous/graalvm-native-17.cfg b/.kokoro/continuous/graalvm-native-17.cfg index cbccd336a..a512b35af 100644 --- a/.kokoro/continuous/graalvm-native-17.cfg +++ b/.kokoro/continuous/graalvm-native-17.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.41.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.41.1" } env_vars: { diff --git a/.kokoro/continuous/graalvm-native.cfg b/.kokoro/continuous/graalvm-native.cfg index d9c9d0205..554601f24 100644 --- a/.kokoro/continuous/graalvm-native.cfg +++ b/.kokoro/continuous/graalvm-native.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.41.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.41.1" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg index 80a629d23..4218cff43 100644 --- a/.kokoro/presubmit/graalvm-native-17.cfg +++ b/.kokoro/presubmit/graalvm-native-17.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.41.0"" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.41.1"" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg index 644bb62ce..b070666b4 100644 --- a/.kokoro/presubmit/graalvm-native.cfg +++ b/.kokoro/presubmit/graalvm-native.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.41.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.41.1" } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8f525e2..3e9380975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [2.46.0](https://github.com/googleapis/java-bigquery/compare/v2.45.0...v2.46.0) (2025-01-11) + + +### Features + +* **bigquery:** Support IAM conditions in datasets in Java client. ([#3602](https://github.com/googleapis/java-bigquery/issues/3602)) ([6696a9c](https://github.com/googleapis/java-bigquery/commit/6696a9c7d42970e3c24bda4da713a855dbe40ce5)) + + +### Bug Fixes + +* NPE when reading BigQueryResultSet from empty tables ([#3627](https://github.com/googleapis/java-bigquery/issues/3627)) ([9a0b05a](https://github.com/googleapis/java-bigquery/commit/9a0b05a3b57797b7cdd8ca9739699fc018dbd868)) +* **test:** Force usage of ReadAPI ([#3625](https://github.com/googleapis/java-bigquery/issues/3625)) ([5ca7d4a](https://github.com/googleapis/java-bigquery/commit/5ca7d4acbbc40d6ef337732464b3bbd130c86430)) + + +### Dependencies + +* Update actions/upload-artifact action to v4.5.0 ([#3620](https://github.com/googleapis/java-bigquery/issues/3620)) ([cc25099](https://github.com/googleapis/java-bigquery/commit/cc25099f81cbf94e9e2ee9db03a7d9ecd913c176)) +* Update actions/upload-artifact action to v4.6.0 ([#3633](https://github.com/googleapis/java-bigquery/issues/3633)) ([ca20aa4](https://github.com/googleapis/java-bigquery/commit/ca20aa47ea7826594975ab6aeb8498e2377f8553)) +* Update dependency com.google.api.grpc:proto-google-cloud-bigqueryconnection-v1 to v2.57.0 ([#3617](https://github.com/googleapis/java-bigquery/issues/3617)) ([51370a9](https://github.com/googleapis/java-bigquery/commit/51370a92e7ab29dfce91199666f23576d2d1b64a)) +* Update dependency com.google.api.grpc:proto-google-cloud-bigqueryconnection-v1 to v2.58.0 ([#3631](https://github.com/googleapis/java-bigquery/issues/3631)) ([b0ea0d5](https://github.com/googleapis/java-bigquery/commit/b0ea0d5bc4ac730b0e2eaf47e8a7441dc113686b)) +* Update dependency com.google.apis:google-api-services-bigquery to v2-rev20241222-2.0.0 ([#3623](https://github.com/googleapis/java-bigquery/issues/3623)) ([4061922](https://github.com/googleapis/java-bigquery/commit/4061922e46135d673bfa48c00bbf284efa46e065)) +* Update dependency com.google.cloud:google-cloud-datacatalog-bom to v1.61.0 ([#3618](https://github.com/googleapis/java-bigquery/issues/3618)) ([6cba626](https://github.com/googleapis/java-bigquery/commit/6cba626ff14cebbc04fa4f6058b273de0c5dd96e)) +* Update dependency com.google.cloud:google-cloud-datacatalog-bom to v1.62.0 ([#3632](https://github.com/googleapis/java-bigquery/issues/3632)) ([e9ff265](https://github.com/googleapis/java-bigquery/commit/e9ff265041f6771a71c8c378ed3ff5fdec6e837b)) +* Update dependency com.google.cloud:sdk-platform-java-config to v3.41.1 ([#3628](https://github.com/googleapis/java-bigquery/issues/3628)) ([442d217](https://github.com/googleapis/java-bigquery/commit/442d217606b7d93d26887344a7a4a01303b18b8c)) +* Update dependency com.google.oauth-client:google-oauth-client-java6 to v1.37.0 ([#3614](https://github.com/googleapis/java-bigquery/issues/3614)) ([f5faa69](https://github.com/googleapis/java-bigquery/commit/f5faa69bc5b6fdae137724df5693f8aecf27d609)) +* Update dependency com.google.oauth-client:google-oauth-client-jetty to v1.37.0 ([#3615](https://github.com/googleapis/java-bigquery/issues/3615)) ([a6c7944](https://github.com/googleapis/java-bigquery/commit/a6c79443a5e675a01ecb91e362e261a6f6ecc055)) +* Update github/codeql-action action to v2.27.9 ([#3608](https://github.com/googleapis/java-bigquery/issues/3608)) ([567ce01](https://github.com/googleapis/java-bigquery/commit/567ce01ed77d44760ddcd872a0d61abdd6a09832)) +* Update github/codeql-action action to v2.28.0 ([#3621](https://github.com/googleapis/java-bigquery/issues/3621)) ([e0e09ec](https://github.com/googleapis/java-bigquery/commit/e0e09ec4954f5b5e2f094e4c67600f38353f453c)) + ## [2.45.0](https://github.com/googleapis/java-bigquery/compare/v2.44.0...v2.45.0) (2024-12-13) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index b6daf595e..18d5535d1 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -6,7 +6,7 @@ google-cloud-bigquery-parent com.google.cloud - 2.45.0 + 2.46.0 @@ -38,6 +38,15 @@ org.apache.maven.plugins maven-compiler-plugin 3.13.0 + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + org.apache.maven.plugins diff --git a/benchmark/src/main/java/com.google.cloud.bigquery/ConnImplBenchmark.java b/benchmark/src/main/java/com.google.cloud.bigquery/ConnImplBenchmark.java index 670e31ff6..eb239463f 100644 --- a/benchmark/src/main/java/com.google.cloud.bigquery/ConnImplBenchmark.java +++ b/benchmark/src/main/java/com.google.cloud.bigquery/ConnImplBenchmark.java @@ -56,9 +56,14 @@ public class ConnImplBenchmark { public void setUp() throws IOException { java.util.logging.Logger.getGlobal().setLevel(Level.ALL); - connectionSettingsReadAPIEnabled = ConnectionSettings.newBuilder().setUseReadAPI(true).build(); - connectionSettingsReadAPIDisabled = - ConnectionSettings.newBuilder().setUseReadAPI(false).build(); + connectionSettingsReadAPIEnabled = ConnectionSettings.newBuilder() + .setUseReadAPI(true) + .setMaxResults(500L) + .setJobTimeoutMs(Long.MAX_VALUE) + .build(); + connectionSettingsReadAPIDisabled = ConnectionSettings.newBuilder() + .setUseReadAPI(false) + .build(); } @Benchmark @@ -153,8 +158,8 @@ private long getResultHash(BigQueryResult bigQueryResultSet) throws SQLException System.out.println("\n Running"); while (rs.next()) { hash += computeHash(rs, "vendor_id", ResultSet::getString); - hash += computeHash(rs, "pickup_datetime", ResultSet::getString); - hash += computeHash(rs, "dropoff_datetime", ResultSet::getString); + hash += computeHash(rs, "pickup_datetime", ResultSet::getLong); + hash += computeHash(rs, "dropoff_datetime", ResultSet::getLong); hash += computeHash(rs, "passenger_count", ResultSet::getLong); hash += computeHash(rs, "trip_distance", ResultSet::getDouble); hash += computeHash(rs, "rate_code", ResultSet::getString); diff --git a/google-cloud-bigquery-bom/pom.xml b/google-cloud-bigquery-bom/pom.xml index 0a689e33f..3326027c8 100644 --- a/google-cloud-bigquery-bom/pom.xml +++ b/google-cloud-bigquery-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-bigquery-bom - 2.45.0 + 2.46.0 pom com.google.cloud sdk-platform-java-config - 3.41.0 + 3.41.1 @@ -54,7 +54,7 @@ com.google.cloud google-cloud-bigquery - 2.45.0 + 2.46.0 diff --git a/google-cloud-bigquery/pom.xml b/google-cloud-bigquery/pom.xml index c040d4fc3..ed717baf0 100644 --- a/google-cloud-bigquery/pom.xml +++ b/google-cloud-bigquery/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-bigquery - 2.45.0 + 2.46.0 jar BigQuery https://github.com/googleapis/java-bigquery @@ -11,7 +11,7 @@ com.google.cloud google-cloud-bigquery-parent - 2.45.0 + 2.46.0 google-cloud-bigquery diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Acl.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Acl.java index e4107cdfd..e6a2a0b91 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Acl.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Acl.java @@ -21,6 +21,7 @@ import com.google.api.core.ApiFunction; import com.google.api.services.bigquery.model.Dataset.Access; import com.google.api.services.bigquery.model.DatasetAccessEntry; +import com.google.api.services.bigquery.model.Expr; import com.google.cloud.StringEnumType; import com.google.cloud.StringEnumValue; import java.io.Serializable; @@ -41,6 +42,7 @@ public final class Acl implements Serializable { private final Entity entity; private final Role role; + private final Expr condition; /** * Dataset roles supported by BigQuery. @@ -568,9 +570,147 @@ Access toPb() { } } + /** Expr represents the conditional information related to dataset access policies. */ + public static final class Expr implements Serializable { + // Textual representation of an expression in Common Expression Language syntax. + private final String expression; + /** + * Optional. Title for the expression, i.e. a short string describing its purpose. This can be + * used e.g. in UIs which allow to enter the expression. + */ + private final String title; + /** + * Optional. Description of the expression. This is a longer text which describes the + * expression, e.g. when hovered over it in a UI. + */ + private final String description; + /** + * Optional. String indicating the location of the expression for error reporting, e.g. a file + * name and a position in the file. + */ + private final String location; + + private static final long serialVersionUID = 7358264726377291156L; + + static final class Builder { + private String expression; + private String title; + private String description; + private String location; + + Builder() {} + + Builder(Expr expr) { + this.expression = expr.expression; + this.title = expr.title; + this.description = expr.description; + this.location = expr.location; + } + + Builder(com.google.api.services.bigquery.model.Expr bqExpr) { + this.expression = bqExpr.getExpression(); + if (bqExpr.getTitle() != null) { + this.title = bqExpr.getTitle(); + } + if (bqExpr.getDescription() != null) { + this.description = bqExpr.getDescription(); + } + if (bqExpr.getLocation() != null) { + this.location = bqExpr.getLocation(); + } + } + + public Builder setExpression(String expression) { + this.expression = expression; + return this; + } + + public Builder setTitle(String title) { + this.title = title; + return this; + } + + public Builder setDescription(String description) { + this.description = description; + return this; + } + + public Builder setLocation(String location) { + this.location = location; + return this; + } + + public Expr build() { + return new Expr(this); + } + } + + public Expr(Builder builder) { + this.expression = builder.expression; + this.title = builder.title; + this.description = builder.description; + this.location = builder.location; + } + + public Expr(String expression, String title, String description, String location) { + this.expression = expression; + this.title = title; + this.description = description; + this.location = location; + } + + com.google.api.services.bigquery.model.Expr toPb() { + com.google.api.services.bigquery.model.Expr bqExpr = + new com.google.api.services.bigquery.model.Expr(); + bqExpr.setExpression(this.expression); + bqExpr.setTitle(this.title); + bqExpr.setDescription(this.description); + bqExpr.setLocation(this.location); + return bqExpr; + } + + static Expr fromPb(com.google.api.services.bigquery.model.Expr bqExpr) { + return new Builder(bqExpr).build(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(expression, title, description, location); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final Expr other = (Expr) obj; + return Objects.equals(this.expression, other.expression) + && Objects.equals(this.title, other.title) + && Objects.equals(this.description, other.description) + && Objects.equals(this.location, other.location); + } + + @Override + public String toString() { + return toPb().toString(); + } + } + private Acl(Entity entity, Role role) { + this(entity, role, null); + } + + private Acl(Entity entity, Role role, Expr condition) { this.entity = checkNotNull(entity); this.role = role; + this.condition = condition; } /** @return Returns the entity for this ACL. */ @@ -582,6 +722,10 @@ public Entity getEntity() { public Role getRole() { return role; } + /** @return Returns the condition specified by this ACL. */ + public Expr getCondition() { + return condition; + } /** * @return Returns an Acl object. @@ -592,6 +736,10 @@ public static Acl of(Entity entity, Role role) { return new Acl(entity, role); } + public static Acl of(Entity entity, Role role, Expr condition) { + return new Acl(entity, role, condition); + } + /** * @param datasetAclEntity * @return Returns an Acl object for a datasetAclEntity. @@ -618,7 +766,7 @@ public static Acl of(Routine routine) { @Override public int hashCode() { - return Objects.hash(entity, role); + return Objects.hash(entity, role, condition); } @Override @@ -635,7 +783,9 @@ public boolean equals(Object obj) { return false; } final Acl other = (Acl) obj; - return Objects.equals(this.entity, other.entity) && Objects.equals(this.role, other.role); + return Objects.equals(this.entity, other.entity) + && Objects.equals(this.role, other.role) + && Objects.equals(this.condition, other.condition); } Access toPb() { @@ -643,11 +793,16 @@ Access toPb() { if (role != null) { accessPb.setRole(role.name()); } + if (condition != null) { + accessPb.setCondition(condition.toPb()); + } return accessPb; } static Acl fromPb(Access access) { return Acl.of( - Entity.fromPb(access), access.getRole() != null ? Role.valueOf(access.getRole()) : null); + Entity.fromPb(access), + access.getRole() != null ? Role.valueOf(access.getRole()) : null, + access.getCondition() != null ? Expr.fromPb(access.getCondition()) : null); } } diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java index 613134fa0..2a7d498c0 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java @@ -289,6 +289,24 @@ public static DatasetOption fields(DatasetField... fields) { return new DatasetOption( BigQueryRpc.Option.FIELDS, Helper.selector(DatasetField.REQUIRED_FIELDS, fields)); } + + /** + * Returns an option to specify the dataset's access policy version for conditional access. If + * this option is not provided the field remains unset and conditional access cannot be used. + * Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests + * for conditional access policy binding in datasets must specify version 3. Datasets with no + * conditional role bindings in access policy may specify any valid value or leave the field + * unset. This field will be mapped to IAM Policy version and will be + * used to fetch the policy from IAM. If unset or if 0 or 1 the value is used for a dataset with + * conditional bindings, access entry with condition will have role string appended by + * 'withcond' string followed by a hash value. Please refer to Troubleshooting + * withcond for more details. + */ + public static DatasetOption accessPolicyVersion(Integer accessPolicyVersion) { + return new DatasetOption(BigQueryRpc.Option.ACCESS_POLICY_VERSION, accessPolicyVersion); + } } /** Class for specifying dataset delete options. */ diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java index e944efceb..a1bb4d406 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java @@ -113,6 +113,9 @@ private class BigQueryResultSet extends AbstractJdbcResultSet { @Override /*Advances the result set to the next row, returning false if no such row exists. Potentially blocking operation*/ public boolean next() throws SQLException { + if (buffer == null) { + return false; + } if (hasReachedEnd) { // if end of stream is reached then we can simply return false return false; } diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Connection.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Connection.java index afc8eb848..83ea0fc0d 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Connection.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Connection.java @@ -140,7 +140,7 @@ ListenableFuture executeSelectAsync(String sql) * @code * ConnectionSettings connectionSettings = * ConnectionSettings.newBuilder() - * ..setUseReadAPI(true) + * .setUseReadAPI(true) * .build(); * Connection connection = bigquery.createConnection(connectionSettings); * String selectQuery = diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java index 57f1a05c0..8b0a83531 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java @@ -59,7 +59,8 @@ enum Option { REQUESTED_POLICY_VERSION("requestedPolicyVersion"), TABLE_METADATA_VIEW("view"), RETRY_OPTIONS("retryOptions"), - BIGQUERY_RETRY_CONFIG("bigQueryRetryConfig"); + BIGQUERY_RETRY_CONFIG("bigQueryRetryConfig"), + ACCESS_POLICY_VERSION("accessPolicyVersion"); private final String value; diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java index 93337d8ca..3946f83f5 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java @@ -130,12 +130,19 @@ private void validateRPC() throws BigQueryException, IOException { public Dataset getDataset(String projectId, String datasetId, Map options) { try { validateRPC(); - return bigquery - .datasets() - .get(projectId, datasetId) - .setFields(Option.FIELDS.getString(options)) - .setPrettyPrint(false) - .execute(); + + Bigquery.Datasets.Get bqGetRequest = + bigquery + .datasets() + .get(projectId, datasetId) + .setFields(Option.FIELDS.getString(options)) + .setPrettyPrint(false); + for (Map.Entry entry : options.entrySet()) { + if (entry.getKey() == Option.ACCESS_POLICY_VERSION && entry.getValue() != null) { + bqGetRequest.setAccessPolicyVersion((Integer) entry.getValue()); + } + } + return bqGetRequest.execute(); } catch (IOException ex) { BigQueryException serviceException = translate(ex); if (serviceException.getCode() == HTTP_NOT_FOUND) { @@ -174,12 +181,18 @@ public Tuple> listDatasets(String projectId, Map options) { try { validateRPC(); - return bigquery - .datasets() - .insert(dataset.getDatasetReference().getProjectId(), dataset) - .setPrettyPrint(false) - .setFields(Option.FIELDS.getString(options)) - .execute(); + Bigquery.Datasets.Insert bqCreateRequest = + bigquery + .datasets() + .insert(dataset.getDatasetReference().getProjectId(), dataset) + .setPrettyPrint(false) + .setFields(Option.FIELDS.getString(options)); + for (Map.Entry entry : options.entrySet()) { + if (entry.getKey() == Option.ACCESS_POLICY_VERSION && entry.getValue() != null) { + bqCreateRequest.setAccessPolicyVersion((Integer) entry.getValue()); + } + } + return bqCreateRequest.execute(); } catch (IOException ex) { throw translate(ex); } @@ -277,12 +290,18 @@ public Dataset patch(Dataset dataset, Map options) { try { validateRPC(); DatasetReference reference = dataset.getDatasetReference(); - return bigquery - .datasets() - .patch(reference.getProjectId(), reference.getDatasetId(), dataset) - .setPrettyPrint(false) - .setFields(Option.FIELDS.getString(options)) - .execute(); + Bigquery.Datasets.Patch bqPatchRequest = + bigquery + .datasets() + .patch(reference.getProjectId(), reference.getDatasetId(), dataset) + .setPrettyPrint(false) + .setFields(Option.FIELDS.getString(options)); + for (Map.Entry entry : options.entrySet()) { + if (entry.getKey() == Option.ACCESS_POLICY_VERSION && entry.getValue() != null) { + bqPatchRequest.setAccessPolicyVersion((Integer) entry.getValue()); + } + } + return bqPatchRequest.execute(); } catch (IOException ex) { throw translate(ex); } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/AclTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/AclTest.java index 30866c2b6..0b53f32ff 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/AclTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/AclTest.java @@ -23,6 +23,7 @@ import com.google.cloud.bigquery.Acl.Domain; import com.google.cloud.bigquery.Acl.Entity; import com.google.cloud.bigquery.Acl.Entity.Type; +import com.google.cloud.bigquery.Acl.Expr; import com.google.cloud.bigquery.Acl.Group; import com.google.cloud.bigquery.Acl.IamMember; import com.google.cloud.bigquery.Acl.Role; @@ -136,4 +137,13 @@ public void testOf() { assertEquals(routine, acl.getEntity()); assertEquals(null, acl.getRole()); } + + @Test + public void testOfWithCondition() { + Expr expr = new Expr("expression", "title", "description", "location"); + Acl acl = Acl.of(Group.ofAllAuthenticatedUsers(), Role.READER, expr); + Dataset.Access pb = acl.toPb(); + assertEquals(acl, Acl.fromPb(pb)); + assertEquals(acl.getCondition(), expr); + } } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java index 88b8f6dbf..c13d272d2 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java @@ -30,6 +30,7 @@ import com.google.cloud.RetryOption; import com.google.cloud.ServiceOptions; import com.google.cloud.Tuple; +import com.google.cloud.bigquery.BigQuery.DatasetOption; import com.google.cloud.bigquery.BigQuery.JobOption; import com.google.cloud.bigquery.BigQuery.QueryResultsOption; import com.google.cloud.bigquery.InsertAllRequest.RowToInsert; @@ -572,6 +573,20 @@ public void testCreateDatasetWithSelectedFields() { verify(bigqueryRpcMock).create(eq(DATASET_INFO_WITH_PROJECT.toPb()), capturedOptions.capture()); } + @Test + public void testCreateDatasetWithAccessPolicy() { + DatasetInfo datasetInfo = DATASET_INFO.setProjectId(OTHER_PROJECT); + DatasetOption datasetOption = DatasetOption.accessPolicyVersion(3); + when(bigqueryRpcMock.create(datasetInfo.toPb(), optionMap(datasetOption))) + .thenReturn(datasetInfo.toPb()); + BigQueryOptions bigQueryOptions = + createBigQueryOptionsForProject(OTHER_PROJECT, rpcFactoryMock); + bigquery = bigQueryOptions.getService(); + Dataset dataset = bigquery.create(datasetInfo, datasetOption); + assertEquals(new Dataset(bigquery, new DatasetInfo.BuilderImpl(datasetInfo)), dataset); + verify(bigqueryRpcMock).create(datasetInfo.toPb(), optionMap(datasetOption)); + } + @Test public void testGetDataset() { when(bigqueryRpcMock.getDataset(PROJECT, DATASET, EMPTY_RPC_OPTIONS)) diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/ConnectionImplTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/ConnectionImplTest.java index 58cb69ba7..7eea1570a 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/ConnectionImplTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/ConnectionImplTest.java @@ -411,7 +411,7 @@ public void testLegacyQuerySinglePage() throws BigQuerySQLException { // calls executeSelect with a nonFast query where the query returns an empty result. @Test - public void testLegacyQuerySinglePageEmptyResults() throws BigQuerySQLException { + public void testLegacyQuerySinglePageEmptyResults() throws BigQuerySQLException, SQLException { ConnectionImpl connectionSpy = Mockito.spy(connection); com.google.api.services.bigquery.model.Job jobResponseMock = new com.google.api.services.bigquery.model.Job() @@ -428,6 +428,10 @@ public void testLegacyQuerySinglePageEmptyResults() throws BigQuerySQLException BigQueryResult res = connectionSpy.executeSelect(SQL_QUERY); assertEquals(res.getTotalRows(), 0); assertEquals(QUERY_SCHEMA, res.getSchema()); + assertEquals( + false, + res.getResultSet() + .next()); // Validates that NPE does not occur when reading from empty ResultSet. verify(bigqueryRpcMock, times(1)) .createJobForQuery(any(com.google.api.services.bigquery.model.Job.class)); } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index 86652c164..24bf84f6e 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -41,6 +41,8 @@ import com.google.cloud.ServiceOptions; import com.google.cloud.bigquery.Acl; import com.google.cloud.bigquery.Acl.DatasetAclEntity; +import com.google.cloud.bigquery.Acl.Expr; +import com.google.cloud.bigquery.Acl.User; import com.google.cloud.bigquery.BigQuery; import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; import com.google.cloud.bigquery.BigQuery.DatasetField; @@ -1219,6 +1221,48 @@ public void testGetDatasetWithSelectedFields() { assertNull(dataset.getMaxTimeTravelHours()); } + @Test + public void testGetDatasetWithAccessPolicyVersion() throws IOException { + String accessPolicyDataset = RemoteBigQueryHelper.generateDatasetName(); + ServiceAccountCredentials credentials = + (ServiceAccountCredentials) GoogleCredentials.getApplicationDefault(); + User user = new User(credentials.getClientEmail()); + Acl.Role role = Acl.Role.WRITER; + Acl.Expr condition = + new Expr( + "request.time > timestamp('2024-01-01T00:00:00Z')", + "test condition", + "requests after the year 2024", + "location"); + Acl acl = Acl.of(user, role, condition); + DatasetOption datasetOption = DatasetOption.accessPolicyVersion(3); + + Dataset dataset = + bigquery.create( + DatasetInfo.newBuilder(accessPolicyDataset) + .setDescription("Some Description") + .setAcl(ImmutableList.of(acl)) + .build(), + datasetOption); + assertThat(dataset).isNotNull(); + + Dataset remoteDataset = bigquery.getDataset(accessPolicyDataset, datasetOption); + assertNotNull(remoteDataset); + assertEquals(dataset.getDescription(), remoteDataset.getDescription()); + assertNotNull(remoteDataset.getCreationTime()); + + Acl remoteAclWithCond = null; + for (Acl remoteAcl : remoteDataset.getAcl()) { + if (remoteAcl.getCondition() != null) { + remoteAclWithCond = remoteAcl; + } + } + assertNotNull(remoteAclWithCond); + assertEquals(remoteAclWithCond.getCondition(), condition); + + RemoteBigQueryHelper.forceDelete(bigquery, accessPolicyDataset); + } + @Test public void testUpdateDataset() { Dataset dataset = @@ -1285,6 +1329,58 @@ public void testUpdateDatasetWithSelectedFields() { assertTrue(dataset.delete()); } + @Test + public void testUpdateDatabaseWithAccessPolicyVersion() throws IOException { + String accessPolicyDataset = RemoteBigQueryHelper.generateDatasetName(); + ServiceAccountCredentials credentials = + (ServiceAccountCredentials) GoogleCredentials.getApplicationDefault(); + Dataset dataset = + bigquery.create( + DatasetInfo.newBuilder(accessPolicyDataset) + .setDescription("Some Description") + .setLabels(Collections.singletonMap("a", "b")) + .build()); + assertThat(dataset).isNotNull(); + + User user = new User(credentials.getClientEmail()); + Acl.Role role = Acl.Role.WRITER; + Acl.Expr condition = + new Expr( + "request.time > timestamp('2024-01-01T00:00:00Z')", + "test condition", + "requests after the year 2024", + "location"); + Acl acl = Acl.of(user, role, condition); + List acls = new ArrayList<>(); + acls.addAll(dataset.getAcl()); + acls.add(acl); + + DatasetOption datasetOption = DatasetOption.accessPolicyVersion(3); + Dataset updatedDataset = + bigquery.update( + dataset + .toBuilder() + .setDescription("Updated Description") + .setLabels(null) + .setAcl(acls) + .build(), + datasetOption); + assertNotNull(updatedDataset); + assertEquals(updatedDataset.getDescription(), "Updated Description"); + assertThat(updatedDataset.getLabels().isEmpty()); + + Acl updatedAclWithCond = null; + for (Acl updatedAcl : updatedDataset.getAcl()) { + if (updatedAcl.getCondition() != null) { + updatedAclWithCond = updatedAcl; + } + } + assertNotNull(updatedAclWithCond); + assertEquals(updatedAclWithCond.getCondition(), condition); + + RemoteBigQueryHelper.forceDelete(bigquery, accessPolicyDataset); + } + @Test public void testGetNonExistingTable() { assertNull(bigquery.getTable(DATASET, "test_get_non_existing_table")); @@ -1688,6 +1784,70 @@ public void testCreateDatasetWithDefaultCollation() { RemoteBigQueryHelper.forceDelete(bigquery, collationDataset); } + @Test + public void testCreateDatasetWithAccessPolicyVersion() throws IOException { + String accessPolicyDataset = RemoteBigQueryHelper.generateDatasetName(); + ServiceAccountCredentials credentials = + (ServiceAccountCredentials) GoogleCredentials.getApplicationDefault(); + User user = new User(credentials.getClientEmail()); + Acl.Role role = Acl.Role.OWNER; + Acl.Expr condition = + new Expr( + "request.time > timestamp('2024-01-01T00:00:00Z')", + "test condition", + "requests after the year 2024", + "location"); + Acl acl = Acl.of(user, role, condition); + DatasetInfo info = + DatasetInfo.newBuilder(accessPolicyDataset) + .setDescription(DESCRIPTION) + .setLabels(LABELS) + .setAcl(ImmutableList.of(acl)) + .build(); + DatasetOption datasetOption = DatasetOption.accessPolicyVersion(3); + Dataset dataset = bigquery.create(info, datasetOption); + assertNotNull(dataset); + assertEquals(dataset.getDescription(), DESCRIPTION); + + Acl remoteAclWithCond = null; + for (Acl remoteAcl : dataset.getAcl()) { + if (remoteAcl.getCondition() != null) { + remoteAclWithCond = remoteAcl; + } + } + assertNotNull(remoteAclWithCond); + assertEquals(remoteAclWithCond.getCondition(), condition); + + RemoteBigQueryHelper.forceDelete(bigquery, accessPolicyDataset); + } + + @Test(expected = BigQueryException.class) + public void testCreateDatabaseWithInvalidAccessPolicyVersion() throws IOException { + String accessPolicyDataset = RemoteBigQueryHelper.generateDatasetName(); + ServiceAccountCredentials credentials = + (ServiceAccountCredentials) GoogleCredentials.getApplicationDefault(); + User user = new User(credentials.getClientEmail()); + Acl.Role role = Acl.Role.READER; + Acl.Expr condition = + new Expr( + "request.time > timestamp('2024-01-01T00:00:00Z')", + "test condition", + "requests after the year 2024", + "location"); + Acl acl = Acl.of(user, role, condition); + DatasetInfo info = + DatasetInfo.newBuilder(accessPolicyDataset) + .setDescription(DESCRIPTION) + .setLabels(LABELS) + .setAcl(ImmutableList.of(acl)) + .build(); + DatasetOption datasetOption = DatasetOption.accessPolicyVersion(4); + Dataset dataset = bigquery.create(info, datasetOption); + assertNotNull(dataset); + + RemoteBigQueryHelper.forceDelete(bigquery, accessPolicyDataset); + } + @Test public void testCreateTableWithDefaultCollation() { String tableName = "test_create_table_with_default_collation"; @@ -3331,6 +3491,24 @@ public void testExecuteSelectDefaultConnectionSettings() throws SQLException { assertEquals(42, bigQueryResult.getTotalRows()); } + @Test + public void testExecuteSelectReadApiEmptyResultSet() throws SQLException { + ConnectionSettings connectionSettings = + ConnectionSettings.newBuilder() + .setJobTimeoutMs( + Long.MAX_VALUE) // Force executeSelect to use ReadAPI instead of fast query. + .setUseReadAPI(true) + .setUseQueryCache(false) + .build(); + Connection connection = bigquery.createConnection(connectionSettings); + String query = "SELECT TIMESTAMP '2022-01-24T23:54:25.095574Z' LIMIT 0"; + BigQueryResult bigQueryResult = connection.executeSelect(query); + + ResultSet rs = bigQueryResult.getResultSet(); + assertThat(rs.next()).isFalse(); + assertThat(bigQueryResult.getTotalRows()).isEqualTo(0); + } + @Test public void testExecuteSelectWithCredentials() throws SQLException { // This test validate that executeSelect uses the same credential provided by the BigQuery diff --git a/pom.xml b/pom.xml index 6fd0cd64e..55b63fffa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-bigquery-parent pom - 2.45.0 + 2.46.0 BigQuery Parent https://github.com/googleapis/java-bigquery @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.41.0 + 3.41.1 @@ -54,7 +54,7 @@ UTF-8 github google-cloud-bigquery-parent - v2-rev20241115-2.0.0 + v2-rev20241222-2.0.0 @@ -71,7 +71,7 @@ com.google.cloud google-cloud-bigquerystorage-bom - 3.11.0 + 3.11.1 pom import @@ -79,7 +79,7 @@ com.google.cloud google-cloud-datacatalog-bom - 1.60.0 + 1.62.0 pom import @@ -93,7 +93,7 @@ com.google.cloud google-cloud-bigquery - 2.45.0 + 2.46.0 @@ -137,19 +137,19 @@ com.google.cloud google-cloud-storage - 2.46.0 + 2.47.0 test com.google.cloud google-cloud-bigqueryconnection - 2.56.0 + 2.58.0 test com.google.api.grpc proto-google-cloud-bigqueryconnection-v1 - 2.56.0 + 2.58.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 78034de31..6badfec9c 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -45,31 +45,31 @@ com.google.cloud google-cloud-bigquery - 2.44.0 + 2.45.0 com.google.oauth-client google-oauth-client-java6 - 1.36.0 + 1.37.0 com.google.oauth-client google-oauth-client-jetty - 1.36.0 + 1.37.0 com.google.cloud google-cloud-bigtable - 2.50.0 + 2.51.0 test com.google.cloud google-cloud-bigqueryconnection - 2.56.0 + 2.58.0 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index d6700af90..291f257b1 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -44,30 +44,30 @@ com.google.cloud google-cloud-bigquery - 2.45.0 + 2.46.0 com.google.oauth-client google-oauth-client-java6 - 1.36.0 + 1.37.0 com.google.oauth-client google-oauth-client-jetty - 1.36.0 + 1.37.0 com.google.cloud google-cloud-bigtable - 2.50.0 + 2.51.0 test com.google.cloud google-cloud-bigqueryconnection - 2.56.0 + 2.58.0 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index f9af31307..bc783dde2 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -66,12 +66,12 @@ com.google.oauth-client google-oauth-client-java6 - 1.36.0 + 1.37.0 com.google.oauth-client google-oauth-client-jetty - 1.36.0 + 1.37.0 @@ -79,13 +79,13 @@ com.google.cloud google-cloud-bigtable - 2.50.0 + 2.51.0 test com.google.cloud google-cloud-bigqueryconnection - 2.56.0 + 2.58.0 test diff --git a/versions.txt b/versions.txt index 13c2e61d7..a944a00ff 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-bigquery:2.45.0:2.45.0 \ No newline at end of file +google-cloud-bigquery:2.46.0:2.46.0 \ No newline at end of file