Skip to content

Commit 6310a63

Browse files
authored
feat(bigtable): Add support for data APIs for materialized views (#2508)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Make sure to open an issue as a [bug/issue](https://togithub.com/googleapis/java-bigtable/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Rollback plan is reviewed and LGTMed - [x] All new data plane features have a completed end to end testing plan Fixes #<issue_number_goes_here> ☕️ If you write sample code, please follow the [samples format]( https://togithub.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md).
1 parent 4e45837 commit 6310a63

File tree

10 files changed

+265
-18
lines changed

10 files changed

+265
-18
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,10 @@
308308
<className>com/google/cloud/bigtable/data/v2/models/Heartbeat</className>
309309
<method>*getEstimatedLowWatermarkTime()</method>
310310
</difference>
311+
<difference>
312+
<!-- TargetId is an InternalApi-->
313+
<differenceType>7012</differenceType>
314+
<className>com/google/cloud/bigtable/data/v2/models/TargetId</className>
315+
<method>*scopedForMaterializedView()</method>
316+
</difference>
311317
</differences>

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/NameUtil.java

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.google.api.core.InternalApi;
1919
import com.google.cloud.bigtable.data.v2.models.AuthorizedViewId;
20+
import com.google.cloud.bigtable.data.v2.models.MaterializedViewId;
2021
import com.google.cloud.bigtable.data.v2.models.TableId;
2122
import com.google.cloud.bigtable.data.v2.models.TargetId;
2223
import java.util.regex.Matcher;
@@ -35,6 +36,8 @@ public class NameUtil {
3536
Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)");
3637
private static final Pattern AUTHORIZED_VIEW_PATTERN =
3738
Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)/authorizedViews/([^/]+)");
39+
private static final Pattern MATERIALIZED_VIEW_PATTERN =
40+
Pattern.compile("projects/([^/]+)/instances/([^/]+)/materializedViews/([^/]+)");
3841

3942
public static String formatInstanceName(@Nonnull String projectId, @Nonnull String instanceId) {
4043
return "projects/" + projectId + "/instances/" + instanceId;
@@ -53,6 +56,11 @@ public static String formatAuthorizedViewName(
5356
return formatTableName(projectId, instanceId, tableId) + "/authorizedViews/" + authorizedViewId;
5457
}
5558

59+
public static String formatMaterializedViewName(
60+
@Nonnull String projectId, @Nonnull String instanceId, @Nonnull String materializedViewId) {
61+
return formatInstanceName(projectId, instanceId) + "/materializedViews/" + materializedViewId;
62+
}
63+
5664
public static String extractTableIdFromTableName(@Nonnull String fullTableName) {
5765
Matcher matcher = TABLE_PATTERN.matcher(fullTableName);
5866
if (!matcher.matches()) {
@@ -88,31 +96,69 @@ public static String extractAuthorizedViewIdFromAuthorizedViewName(
8896
return matcher.group(4);
8997
}
9098

91-
/** A helper to convert fully qualified tableName and authorizedViewName to a {@link TargetId} */
99+
public static String extractMaterializedViewIdFromMaterializedViewName(
100+
@Nonnull String fullMaterializedViewName) {
101+
Matcher matcher = MATERIALIZED_VIEW_PATTERN.matcher(fullMaterializedViewName);
102+
if (!matcher.matches()) {
103+
throw new IllegalArgumentException(
104+
"Invalid materialized view name: " + fullMaterializedViewName);
105+
}
106+
return matcher.group(3);
107+
}
108+
109+
/** A helper to convert fully qualified tableName andauthorizedViewName to a {@link TargetId} */
92110
public static TargetId extractTargetId(
93111
@Nonnull String tableName, @Nonnull String authorizedViewName) {
94-
if (tableName.isEmpty() && authorizedViewName.isEmpty()) {
112+
return extractTargetId(tableName, authorizedViewName, "");
113+
}
114+
115+
/**
116+
* A helper to convert fully qualified tableName, authorizedViewName and materializedViewName to a
117+
* {@link TargetId}
118+
*/
119+
public static TargetId extractTargetId(
120+
@Nonnull String tableName,
121+
@Nonnull String authorizedViewName,
122+
@Nonnull String materializedViewName) {
123+
if (tableName.isEmpty() && authorizedViewName.isEmpty() && materializedViewName.isEmpty()) {
95124
throw new IllegalArgumentException(
96-
"Either table name or authorized view name must be specified. Table name: "
125+
"Either table name, authorized view name or materialized view name must be specified. Table name: "
97126
+ tableName
98127
+ ", authorized view name: "
99-
+ authorizedViewName);
128+
+ authorizedViewName
129+
+ ", materialized view name: "
130+
+ materializedViewName);
131+
}
132+
int names = 0;
133+
if (!tableName.isEmpty()) {
134+
++names;
135+
}
136+
if (!authorizedViewName.isEmpty()) {
137+
++names;
138+
}
139+
if (!materializedViewName.isEmpty()) {
140+
++names;
100141
}
101-
if (!tableName.isEmpty() && !authorizedViewName.isEmpty()) {
142+
if (names > 1) {
102143
throw new IllegalArgumentException(
103-
"Table name and authorized view name cannot be specified at the same time. Table name: "
144+
"Only one of table name, authorized view name and materialized view name can be specified at the same time. Table name: "
104145
+ tableName
105146
+ ", authorized view name: "
106-
+ authorizedViewName);
147+
+ authorizedViewName
148+
+ ", materialized view name: "
149+
+ materializedViewName);
107150
}
108151

109152
if (!tableName.isEmpty()) {
110153
String tableId = extractTableIdFromTableName(tableName);
111154
return TableId.of(tableId);
112-
} else {
155+
} else if (!authorizedViewName.isEmpty()) {
113156
String tableId = extractTableIdFromAuthorizedViewName(authorizedViewName);
114157
String authorizedViewId = extractAuthorizedViewIdFromAuthorizedViewName(authorizedViewName);
115158
return AuthorizedViewId.of(tableId, authorizedViewId);
116159
}
160+
String materializedViewId =
161+
extractMaterializedViewIdFromMaterializedViewName(materializedViewName);
162+
return MaterializedViewId.of(materializedViewId);
117163
}
118164
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/AuthorizedViewId.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,10 @@ public String toResourceName(String projectId, String instanceId) {
5252
public boolean scopedForAuthorizedView() {
5353
return true;
5454
}
55+
56+
@Override
57+
@InternalApi
58+
public boolean scopedForMaterializedView() {
59+
return false;
60+
}
5561
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigtable.data.v2.models;
18+
19+
import com.google.api.core.InternalApi;
20+
import com.google.auto.value.AutoValue;
21+
import com.google.cloud.bigtable.data.v2.internal.NameUtil;
22+
import com.google.common.base.Preconditions;
23+
24+
/**
25+
* An implementation of a {@link TargetId} for materialized views.
26+
*
27+
* <p>See {@link com.google.cloud.bigtable.admin.v2.models.MaterializedView} for more details about
28+
* an materialized view.
29+
*/
30+
@AutoValue
31+
public abstract class MaterializedViewId implements TargetId {
32+
/** Constructs a new MaterializedViewId object from the specified materializedViewId. */
33+
public static MaterializedViewId of(String materializedViewId) {
34+
Preconditions.checkNotNull(materializedViewId, "materialized view id can't be null.");
35+
return new AutoValue_MaterializedViewId(materializedViewId);
36+
}
37+
38+
abstract String getMaterializedViewId();
39+
40+
@Override
41+
@InternalApi
42+
public String toResourceName(String projectId, String instanceId) {
43+
return NameUtil.formatMaterializedViewName(projectId, instanceId, getMaterializedViewId());
44+
}
45+
46+
@Override
47+
@InternalApi
48+
public boolean scopedForAuthorizedView() {
49+
return false;
50+
}
51+
52+
@Override
53+
@InternalApi
54+
public boolean scopedForMaterializedView() {
55+
return true;
56+
}
57+
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Query.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static Query create(String tableId) {
6262
* com.google.cloud.bigtable.data.v2.BigtableDataSettings}.
6363
*
6464
* @see AuthorizedViewId
65+
* @see MaterializedViewId
6566
* @see TableId
6667
*/
6768
public static Query create(TargetId targetId) {
@@ -317,7 +318,9 @@ public ByteStringRange getBound() {
317318
public ReadRowsRequest toProto(RequestContext requestContext) {
318319
String resourceName =
319320
targetId.toResourceName(requestContext.getProjectId(), requestContext.getInstanceId());
320-
if (targetId.scopedForAuthorizedView()) {
321+
if (targetId.scopedForMaterializedView()) {
322+
builder.setMaterializedViewName(resourceName);
323+
} else if (targetId.scopedForAuthorizedView()) {
321324
builder.setAuthorizedViewName(resourceName);
322325
} else {
323326
builder.setTableName(resourceName);
@@ -335,8 +338,10 @@ public static Query fromProto(@Nonnull ReadRowsRequest request) {
335338
Preconditions.checkArgument(request != null, "ReadRowsRequest must not be null");
336339
String tableName = request.getTableName();
337340
String authorizedViewName = request.getAuthorizedViewName();
341+
String materializedViewName = request.getMaterializedViewName();
338342

339-
Query query = new Query(NameUtil.extractTargetId(tableName, authorizedViewName));
343+
Query query =
344+
new Query(NameUtil.extractTargetId(tableName, authorizedViewName, materializedViewName));
340345
query.builder = request.toBuilder();
341346

342347
return query;

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/SampleRowKeysRequest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ public com.google.bigtable.v2.SampleRowKeysRequest toProto(RequestContext reques
4444
com.google.bigtable.v2.SampleRowKeysRequest.newBuilder();
4545
String resourceName =
4646
targetId.toResourceName(requestContext.getProjectId(), requestContext.getInstanceId());
47-
if (targetId.scopedForAuthorizedView()) {
47+
if (targetId.scopedForMaterializedView()) {
48+
builder.setMaterializedViewName(resourceName);
49+
} else if (targetId.scopedForAuthorizedView()) {
4850
builder.setAuthorizedViewName(resourceName);
4951
} else {
5052
builder.setTableName(resourceName);
@@ -55,17 +57,19 @@ public com.google.bigtable.v2.SampleRowKeysRequest toProto(RequestContext reques
5557
/**
5658
* Wraps the protobuf {@link com.google.bigtable.v2.SampleRowKeysRequest}.
5759
*
58-
* <p>WARNING: Please note that the project id & instance id in the table/authorized view name
59-
* will be overwritten by the configuration in the BigtableDataClient.
60+
* <p>WARNING: Please note that the project id & instance id in the table/authorized
61+
* view/materialized view name will be overwritten by the configuration in the BigtableDataClient.
6062
*/
6163
@InternalApi
6264
public static SampleRowKeysRequest fromProto(
6365
@Nonnull com.google.bigtable.v2.SampleRowKeysRequest request) {
6466
String tableName = request.getTableName();
6567
String authorizedViewName = request.getAuthorizedViewName();
68+
String materializedViewName = request.getMaterializedViewName();
6669

6770
SampleRowKeysRequest sampleRowKeysRequest =
68-
SampleRowKeysRequest.create(NameUtil.extractTargetId(tableName, authorizedViewName));
71+
SampleRowKeysRequest.create(
72+
NameUtil.extractTargetId(tableName, authorizedViewName, materializedViewName));
6973

7074
return sampleRowKeysRequest;
7175
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TableId.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,10 @@ public String toResourceName(String projectId, String instanceId) {
4444
public boolean scopedForAuthorizedView() {
4545
return false;
4646
}
47+
48+
@Override
49+
@InternalApi
50+
public boolean scopedForMaterializedView() {
51+
return false;
52+
}
4753
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/TargetId.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,15 @@ public interface TargetId extends Serializable {
4040

4141
/**
4242
* Returns true if this TargetId object represents id for an authorized view (rather than a
43-
* table).
43+
* table/materialized view).
4444
*/
4545
@InternalApi
4646
boolean scopedForAuthorizedView();
47+
48+
/**
49+
* Returns true if this TargetId object represents id for an materialized view (rather than a
50+
* table/authorized view).
51+
*/
52+
@InternalApi
53+
boolean scopedForMaterializedView();
4754
}

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static com.google.common.truth.Truth.assertThat;
1919

2020
import com.google.cloud.bigtable.data.v2.models.AuthorizedViewId;
21+
import com.google.cloud.bigtable.data.v2.models.MaterializedViewId;
2122
import com.google.cloud.bigtable.data.v2.models.TableId;
2223
import org.junit.Rule;
2324
import org.junit.Test;
@@ -102,23 +103,64 @@ public void extractTableNameFromAuthorizedViewNameTest() {
102103
}
103104

104105
@Test
105-
public void testExtractTargetId() {
106+
public void testExtractTargetId2() {
106107
String testTableName = "projects/my-project/instances/my-instance/tables/my-table";
107108
String testAuthorizedViewName =
108109
"projects/my-project/instances/my-instance/tables/my-table/authorizedViews/my-authorized-view";
109110
assertThat(
110-
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(testTableName, ""))
111+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
112+
testTableName, "", ""))
111113
.isEqualTo(TableId.of("my-table"));
112114
assertThat(
113115
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
114-
"", testAuthorizedViewName))
116+
"", testAuthorizedViewName, ""))
115117
.isEqualTo(AuthorizedViewId.of("my-table", "my-authorized-view"));
116118

119+
// No name is provided
117120
exception.expect(IllegalArgumentException.class);
118121
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId("", "");
119122

123+
// Multiple names are provided
120124
exception.expect(IllegalArgumentException.class);
121125
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
122126
testTableName, testAuthorizedViewName);
123127
}
128+
129+
@Test
130+
public void testExtractTargetId3() {
131+
String testTableName = "projects/my-project/instances/my-instance/tables/my-table";
132+
String testAuthorizedViewName =
133+
"projects/my-project/instances/my-instance/tables/my-table/authorizedViews/my-authorized-view";
134+
String testMaterializedViewName =
135+
"projects/my-project/instances/my-instance/materializedViews/my-materialized-view";
136+
assertThat(
137+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
138+
testTableName, "", ""))
139+
.isEqualTo(TableId.of("my-table"));
140+
assertThat(
141+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
142+
"", testAuthorizedViewName, ""))
143+
.isEqualTo(AuthorizedViewId.of("my-table", "my-authorized-view"));
144+
assertThat(
145+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
146+
"", "", testMaterializedViewName))
147+
.isEqualTo(MaterializedViewId.of("my-materialized-view"));
148+
149+
// No name is provided
150+
exception.expect(IllegalArgumentException.class);
151+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId("", "", "");
152+
153+
// Multiple names are provided
154+
exception.expect(IllegalArgumentException.class);
155+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
156+
testTableName, testAuthorizedViewName, "");
157+
158+
exception.expect(IllegalArgumentException.class);
159+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
160+
testTableName, "", testMaterializedViewName);
161+
162+
exception.expect(IllegalArgumentException.class);
163+
com.google.cloud.bigtable.data.v2.internal.NameUtil.extractTargetId(
164+
"", testAuthorizedViewName, testMaterializedViewName);
165+
}
124166
}

0 commit comments

Comments
 (0)