diff --git a/google-cloud-bigtable/clirr-ignored-differences.xml b/google-cloud-bigtable/clirr-ignored-differences.xml index 4052e1b4a7..83a97c27e8 100644 --- a/google-cloud-bigtable/clirr-ignored-differences.xml +++ b/google-cloud-bigtable/clirr-ignored-differences.xml @@ -308,4 +308,10 @@ com/google/cloud/bigtable/data/v2/models/Heartbeat *getEstimatedLowWatermarkTime() + + + 7012 + com/google/cloud/bigtable/data/v2/models/TargetId + *scopedForMaterializedView() + diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/NameUtil.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/NameUtil.java index 68c66067b1..27a7e4f87d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/NameUtil.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/NameUtil.java @@ -17,6 +17,7 @@ import com.google.api.core.InternalApi; import com.google.cloud.bigtable.data.v2.models.AuthorizedViewId; +import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.TableId; import com.google.cloud.bigtable.data.v2.models.TargetId; import java.util.regex.Matcher; @@ -35,6 +36,8 @@ public class NameUtil { Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)"); private static final Pattern AUTHORIZED_VIEW_PATTERN = Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)/authorizedViews/([^/]+)"); + private static final Pattern MATERIALIZED_VIEW_PATTERN = + Pattern.compile("projects/([^/]+)/instances/([^/]+)/materializedViews/([^/]+)"); public static String formatInstanceName(@Nonnull String projectId, @Nonnull String instanceId) { return "projects/" + projectId + "/instances/" + instanceId; @@ -53,6 +56,11 @@ public static String formatAuthorizedViewName( return formatTableName(projectId, instanceId, tableId) + "/authorizedViews/" + authorizedViewId; } + public static String formatMaterializedViewName( + @Nonnull String projectId, @Nonnull String instanceId, @Nonnull String materializedViewId) { + return formatInstanceName(projectId, instanceId) + "/materializedViews/" + materializedViewId; + } + public static String extractTableIdFromTableName(@Nonnull String fullTableName) { Matcher matcher = TABLE_PATTERN.matcher(fullTableName); if (!matcher.matches()) { @@ -88,31 +96,69 @@ public static String extractAuthorizedViewIdFromAuthorizedViewName( return matcher.group(4); } - /** A helper to convert fully qualified tableName and authorizedViewName to a {@link TargetId} */ + public static String extractMaterializedViewIdFromMaterializedViewName( + @Nonnull String fullMaterializedViewName) { + Matcher matcher = MATERIALIZED_VIEW_PATTERN.matcher(fullMaterializedViewName); + if (!matcher.matches()) { + throw new IllegalArgumentException( + "Invalid materialized view name: " + fullMaterializedViewName); + } + return matcher.group(3); + } + + /** A helper to convert fully qualified tableName andauthorizedViewName to a {@link TargetId} */ public static TargetId extractTargetId( @Nonnull String tableName, @Nonnull String authorizedViewName) { - if (tableName.isEmpty() && authorizedViewName.isEmpty()) { + return extractTargetId(tableName, authorizedViewName, ""); + } + + /** + * A helper to convert fully qualified tableName, authorizedViewName and materializedViewName to a + * {@link TargetId} + */ + public static TargetId extractTargetId( + @Nonnull String tableName, + @Nonnull String authorizedViewName, + @Nonnull String materializedViewName) { + if (tableName.isEmpty() && authorizedViewName.isEmpty() && materializedViewName.isEmpty()) { throw new IllegalArgumentException( - "Either table name or authorized view name must be specified. Table name: " + "Either table name, authorized view name or materialized view name must be specified. Table name: " + tableName + ", authorized view name: " - + authorizedViewName); + + authorizedViewName + + ", materialized view name: " + + materializedViewName); + } + int names = 0; + if (!tableName.isEmpty()) { + ++names; + } + if (!authorizedViewName.isEmpty()) { + ++names; + } + if (!materializedViewName.isEmpty()) { + ++names; } - if (!tableName.isEmpty() && !authorizedViewName.isEmpty()) { + if (names > 1) { throw new IllegalArgumentException( - "Table name and authorized view name cannot be specified at the same time. Table name: " + "Only one of table name, authorized view name and materialized view name can be specified at the same time. Table name: " + tableName + ", authorized view name: " - + authorizedViewName); + + authorizedViewName + + ", materialized view name: " + + materializedViewName); } if (!tableName.isEmpty()) { String tableId = extractTableIdFromTableName(tableName); return TableId.of(tableId); - } else { + } else if (!authorizedViewName.isEmpty()) { String tableId = extractTableIdFromAuthorizedViewName(authorizedViewName); String authorizedViewId = extractAuthorizedViewIdFromAuthorizedViewName(authorizedViewName); return AuthorizedViewId.of(tableId, authorizedViewId); } + String materializedViewId = + extractMaterializedViewIdFromMaterializedViewName(materializedViewName); + return MaterializedViewId.of(materializedViewId); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/AuthorizedViewId.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/AuthorizedViewId.java index 5f64190b5c..27b3819111 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/AuthorizedViewId.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/AuthorizedViewId.java @@ -52,4 +52,10 @@ public String toResourceName(String projectId, String instanceId) { public boolean scopedForAuthorizedView() { return true; } + + @Override + @InternalApi + public boolean scopedForMaterializedView() { + return false; + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewId.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewId.java new file mode 100644 index 0000000000..7e735c37b3 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewId.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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 + * + * https://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.bigtable.data.v2.models; + +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.cloud.bigtable.data.v2.internal.NameUtil; +import com.google.common.base.Preconditions; + +/** + * An implementation of a {@link TargetId} for materialized views. + * + *

See {@link com.google.cloud.bigtable.admin.v2.models.MaterializedView} for more details about + * an materialized view. + */ +@AutoValue +public abstract class MaterializedViewId implements TargetId { + /** Constructs a new MaterializedViewId object from the specified materializedViewId. */ + public static MaterializedViewId of(String materializedViewId) { + Preconditions.checkNotNull(materializedViewId, "materialized view id can't be null."); + return new AutoValue_MaterializedViewId(materializedViewId); + } + + abstract String getMaterializedViewId(); + + @Override + @InternalApi + public String toResourceName(String projectId, String instanceId) { + return NameUtil.formatMaterializedViewName(projectId, instanceId, getMaterializedViewId()); + } + + @Override + @InternalApi + public boolean scopedForAuthorizedView() { + return false; + } + + @Override + @InternalApi + public boolean scopedForMaterializedView() { + return true; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Query.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Query.java index 1b4cb8d680..63ec9c1316 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Query.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Query.java @@ -62,6 +62,7 @@ public static Query create(String tableId) { * com.google.cloud.bigtable.data.v2.BigtableDataSettings}. * * @see AuthorizedViewId + * @see MaterializedViewId * @see TableId */ public static Query create(TargetId targetId) { @@ -317,7 +318,9 @@ public ByteStringRange getBound() { public ReadRowsRequest toProto(RequestContext requestContext) { String resourceName = targetId.toResourceName(requestContext.getProjectId(), requestContext.getInstanceId()); - if (targetId.scopedForAuthorizedView()) { + if (targetId.scopedForMaterializedView()) { + builder.setMaterializedViewName(resourceName); + } else if (targetId.scopedForAuthorizedView()) { builder.setAuthorizedViewName(resourceName); } else { builder.setTableName(resourceName); @@ -335,8 +338,10 @@ public static Query fromProto(@Nonnull ReadRowsRequest request) { Preconditions.checkArgument(request != null, "ReadRowsRequest must not be null"); String tableName = request.getTableName(); String authorizedViewName = request.getAuthorizedViewName(); + String materializedViewName = request.getMaterializedViewName(); - Query query = new Query(NameUtil.extractTargetId(tableName, authorizedViewName)); + Query query = + new Query(NameUtil.extractTargetId(tableName, authorizedViewName, materializedViewName)); query.builder = request.toBuilder(); return query; diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/SampleRowKeysRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/SampleRowKeysRequest.java index 08d9a3ca23..d2a9dcb6a7 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/SampleRowKeysRequest.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/SampleRowKeysRequest.java @@ -44,7 +44,9 @@ public com.google.bigtable.v2.SampleRowKeysRequest toProto(RequestContext reques com.google.bigtable.v2.SampleRowKeysRequest.newBuilder(); String resourceName = targetId.toResourceName(requestContext.getProjectId(), requestContext.getInstanceId()); - if (targetId.scopedForAuthorizedView()) { + if (targetId.scopedForMaterializedView()) { + builder.setMaterializedViewName(resourceName); + } else if (targetId.scopedForAuthorizedView()) { builder.setAuthorizedViewName(resourceName); } else { builder.setTableName(resourceName); @@ -55,17 +57,19 @@ public com.google.bigtable.v2.SampleRowKeysRequest toProto(RequestContext reques /** * Wraps the protobuf {@link com.google.bigtable.v2.SampleRowKeysRequest}. * - *

WARNING: Please note that the project id & instance id in the table/authorized view name - * will be overwritten by the configuration in the BigtableDataClient. + *

WARNING: Please note that the project id & instance id in the table/authorized + * view/materialized view name will be overwritten by the configuration in the BigtableDataClient. */ @InternalApi public static SampleRowKeysRequest fromProto( @Nonnull com.google.bigtable.v2.SampleRowKeysRequest request) { String tableName = request.getTableName(); String authorizedViewName = request.getAuthorizedViewName(); + String materializedViewName = request.getMaterializedViewName(); SampleRowKeysRequest sampleRowKeysRequest = - SampleRowKeysRequest.create(NameUtil.extractTargetId(tableName, authorizedViewName)); + SampleRowKeysRequest.create( + NameUtil.extractTargetId(tableName, authorizedViewName, materializedViewName)); return sampleRowKeysRequest; } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TableId.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TableId.java index 15b2cd9d95..1b19e75d69 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TableId.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TableId.java @@ -44,4 +44,10 @@ public String toResourceName(String projectId, String instanceId) { public boolean scopedForAuthorizedView() { return false; } + + @Override + @InternalApi + public boolean scopedForMaterializedView() { + return false; + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TargetId.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TargetId.java index ae5be23598..73860dc1d8 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TargetId.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TargetId.java @@ -40,8 +40,15 @@ public interface TargetId extends Serializable { /** * Returns true if this TargetId object represents id for an authorized view (rather than a - * table). + * table/materialized view). */ @InternalApi boolean scopedForAuthorizedView(); + + /** + * Returns true if this TargetId object represents id for an materialized view (rather than a + * table/authorized view). + */ + @InternalApi + boolean scopedForMaterializedView(); } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java index 7622ce5dfa..b21aa463c2 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.cloud.bigtable.data.v2.models.AuthorizedViewId; +import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.TableId; import org.junit.Rule; import org.junit.Test; @@ -102,23 +103,64 @@ public void extractTableNameFromAuthorizedViewNameTest() { } @Test - public void testExtractTargetId() { + public void testExtractTargetId2() { String testTableName = "projects/my-project/instances/my-instance/tables/my-table"; String testAuthorizedViewName = "projects/my-project/instances/my-instance/tables/my-table/authorizedViews/my-authorized-view"; assertThat( - com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(testTableName, "")) + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + testTableName, "", "")) .isEqualTo(TableId.of("my-table")); assertThat( com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( - "", testAuthorizedViewName)) + "", testAuthorizedViewName, "")) .isEqualTo(AuthorizedViewId.of("my-table", "my-authorized-view")); + // No name is provided exception.expect(IllegalArgumentException.class); com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId("", ""); + // Multiple names are provided exception.expect(IllegalArgumentException.class); com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( testTableName, testAuthorizedViewName); } + + @Test + public void testExtractTargetId3() { + String testTableName = "projects/my-project/instances/my-instance/tables/my-table"; + String testAuthorizedViewName = + "projects/my-project/instances/my-instance/tables/my-table/authorizedViews/my-authorized-view"; + String testMaterializedViewName = + "projects/my-project/instances/my-instance/materializedViews/my-materialized-view"; + assertThat( + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + testTableName, "", "")) + .isEqualTo(TableId.of("my-table")); + assertThat( + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + "", testAuthorizedViewName, "")) + .isEqualTo(AuthorizedViewId.of("my-table", "my-authorized-view")); + assertThat( + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + "", "", testMaterializedViewName)) + .isEqualTo(MaterializedViewId.of("my-materialized-view")); + + // No name is provided + exception.expect(IllegalArgumentException.class); + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId("", "", ""); + + // Multiple names are provided + exception.expect(IllegalArgumentException.class); + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + testTableName, testAuthorizedViewName, ""); + + exception.expect(IllegalArgumentException.class); + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + testTableName, "", testMaterializedViewName); + + exception.expect(IllegalArgumentException.class); + com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId( + "", testAuthorizedViewName, testMaterializedViewName); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewIdTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewIdTest.java new file mode 100644 index 0000000000..8978f8ff99 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MaterializedViewIdTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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 + * + * https://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.bigtable.data.v2.models; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MaterializedViewIdTest { + private static final String PROJECT_ID = "my-project"; + private static final String INSTANCE_ID = "my-instance"; + private static final String TABLE_ID = "my-table"; + private static final String MATERIALIZED_VIEW_ID = "my-materialized-view"; + + @Test + public void testToResourceName() { + MaterializedViewId authorizedViewId = MaterializedViewId.of(MATERIALIZED_VIEW_ID); + + assertThat(authorizedViewId.toResourceName(PROJECT_ID, INSTANCE_ID)) + .isEqualTo( + "projects/my-project/instances/my-instance/materializedViews/my-materialized-view"); + } + + @Test + public void testEquality() { + MaterializedViewId authorizedViewId = MaterializedViewId.of(MATERIALIZED_VIEW_ID); + + assertThat(authorizedViewId).isEqualTo(MaterializedViewId.of(MATERIALIZED_VIEW_ID)); + assertThat(authorizedViewId).isNotEqualTo(MaterializedViewId.of("another-materialized-view")); + assertThat(authorizedViewId).isNotEqualTo(TableId.of(TABLE_ID)); + } + + @Test + public void testHashCode() { + MaterializedViewId authorizedViewId = MaterializedViewId.of(MATERIALIZED_VIEW_ID); + + assertThat(authorizedViewId.hashCode()) + .isEqualTo(MaterializedViewId.of(MATERIALIZED_VIEW_ID).hashCode()); + assertThat(authorizedViewId.hashCode()) + .isNotEqualTo(MaterializedViewId.of("another-materialized-view").hashCode()); + assertThat(authorizedViewId.hashCode()).isNotEqualTo(TableId.of(TABLE_ID).hashCode()); + } + + @Test + public void testToString() { + MaterializedViewId authorizedViewId = MaterializedViewId.of(MATERIALIZED_VIEW_ID); + + assertThat(authorizedViewId.toString()) + .isEqualTo("MaterializedViewId{materializedViewId=my-materialized-view}"); + } +}