From ca6d807d9ed349eb312608b20c8f923a85e6ddc2 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Tue, 28 Jul 2026 01:29:36 +0000 Subject: [PATCH 1/2] fix(bigtable): add materialized view routing param to ReadRows and SampleRowKeys ReadRows and SampleRowKeys can target a materialized view, but the client's request-params extractor only emitted table_name + app_profile_id and ignored materialized_view_name. Per the google.api.routing annotation in bigtable.proto, materialized views are instance-scoped and must route on the instance name. Split the header composition into composeReadRequestParams (materialized-view capable) and composeWriteRequestParams (table/authorized-view only), and add NameUtil.extractInstanceNameFromMaterializedViewName. Add HeadersTest coverage asserting the instance-name routing param for both operations, and a ReadIT integration test that reads through a materialized view. --- .../data/v2/stub/EnhancedBigtableStub.java | 37 ++++++++++-- .../cloud/bigtable/data/v2/it/ReadIT.java | 58 +++++++++++++++++++ .../bigtable/data/v2/stub/HeadersTest.java | 17 ++++++ 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index aa01e0a57f37..91cd35c34bcb 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -63,6 +63,7 @@ import com.google.cloud.bigtable.data.v2.internal.PrepareResponse; import com.google.cloud.bigtable.data.v2.internal.RequestContext; import com.google.cloud.bigtable.data.v2.internal.SqlRow; +import com.google.cloud.bigtable.data.v2.internal.api.MaterializedViewName; import com.google.cloud.bigtable.data.v2.internal.csm.tracers.BigtableTracerStreamingCallable; import com.google.cloud.bigtable.data.v2.internal.csm.tracers.BigtableTracerUnaryCallable; import com.google.cloud.bigtable.data.v2.internal.csm.tracers.TracedBatcherUnaryCallable; @@ -374,8 +375,16 @@ private ServerStreamingCallable createReadRo .setMethodDescriptor(BigtableGrpc.getReadRowsMethod()) .setParamsExtractor( r -> - composeRequestParams( - r.getAppProfileId(), r.getTableName(), r.getAuthorizedViewName())) + r.getMaterializedViewName().isEmpty() + ? composeRequestParams( + r.getAppProfileId(), + r.getTableName(), + r.getAuthorizedViewName()) + : composeInstanceLevelRequestParams( + MaterializedViewName.parse(r.getMaterializedViewName()) + .getInstanceName() + .toString(), + r.getAppProfileId())) .build(), readRowsSettings.getRetryableCodes()); @@ -448,8 +457,16 @@ public ServerStreamingCallable createSkipLargeRowsCall .setMethodDescriptor(BigtableGrpc.getReadRowsMethod()) .setParamsExtractor( r -> - composeRequestParams( - r.getAppProfileId(), r.getTableName(), r.getAuthorizedViewName())) + r.getMaterializedViewName().isEmpty() + ? composeRequestParams( + r.getAppProfileId(), + r.getTableName(), + r.getAuthorizedViewName()) + : composeInstanceLevelRequestParams( + MaterializedViewName.parse(r.getMaterializedViewName()) + .getInstanceName() + .toString(), + r.getAppProfileId())) .build(), readRowsSettings.getRetryableCodes()); @@ -598,8 +615,16 @@ public ApiFuture> futureCall(String s, ApiCallContext apiCallCon .setMethodDescriptor(BigtableGrpc.getSampleRowKeysMethod()) .setParamsExtractor( r -> - composeRequestParams( - r.getAppProfileId(), r.getTableName(), r.getAuthorizedViewName())) + r.getMaterializedViewName().isEmpty() + ? composeRequestParams( + r.getAppProfileId(), + r.getTableName(), + r.getAuthorizedViewName()) + : composeInstanceLevelRequestParams( + MaterializedViewName.parse(r.getMaterializedViewName()) + .getInstanceName() + .toString(), + r.getAppProfileId())) .build(), perOpSettings.sampleRowKeysSettings.getRetryableCodes()); diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java index 51adc4abb63f..10160d299e0a 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java @@ -30,11 +30,16 @@ import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.rpc.ResponseObserver; import com.google.api.gax.rpc.StreamController; +import com.google.cloud.bigtable.admin.v2.BigtableInstanceAdminClient; +import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; import com.google.cloud.bigtable.admin.v2.models.AuthorizedView; +import com.google.cloud.bigtable.admin.v2.models.CreateMaterializedViewRequest; +import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest; import com.google.cloud.bigtable.data.v2.BigtableDataClient; import com.google.cloud.bigtable.data.v2.BigtableDataSettings; import com.google.cloud.bigtable.data.v2.models.AuthorizedViewId; import com.google.cloud.bigtable.data.v2.models.BulkMutation; +import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.Query; import com.google.cloud.bigtable.data.v2.models.Range.ByteStringRange; import com.google.cloud.bigtable.data.v2.models.Row; @@ -43,6 +48,7 @@ import com.google.cloud.bigtable.data.v2.models.RowMutationEntry; import com.google.cloud.bigtable.data.v2.models.TableId; import com.google.cloud.bigtable.test_helpers.env.EmulatorEnv; +import com.google.cloud.bigtable.test_helpers.env.PrefixGenerator; import com.google.cloud.bigtable.test_helpers.env.TestEnvRule; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -219,6 +225,58 @@ public void readEmptyOnAuthorizedView() throws Throwable { .deleteAuthorizedView(tableId.getTableId(), testAuthorizedView.getId()); } + @Test + public void readFromMaterializedView() throws Throwable { + assume() + .withMessage("MaterializedView is not supported on Emulator") + .that(testEnvRule.env()) + .isNotInstanceOf(EmulatorEnv.class); + + BigtableDataClient dataClient = testEnvRule.env().getDataClient(); + BigtableTableAdminClient tableAdmin = testEnvRule.env().getTableAdminClient(); + BigtableInstanceAdminClient instanceAdmin = testEnvRule.env().getInstanceAdminClient(); + String instanceId = testEnvRule.env().getInstanceId(); + + String tableId = PrefixGenerator.newPrefix("ReadIT#readFromMaterializedView"); + String materializedViewId = PrefixGenerator.newPrefix("ReadIT#readFromMaterializedView"); + String rowKey = prefix + "-readFromMaterializedView"; + + tableAdmin.createTable(CreateTableRequest.of(tableId).addFamily("cf1")); + try { + dataClient.mutateRow( + RowMutation.create(TableId.of(tableId), rowKey).setCell("cf1", "column", "value")); + + instanceAdmin.createMaterializedView( + CreateMaterializedViewRequest.of(instanceId, materializedViewId) + .setQuery( + "SELECT _key, MAX(cf1['column']) as column FROM `" + + tableId + + "` GROUP BY _key") + .setDeletionProtection(false)); + try { + Row row = null; + long[] backoffSeconds = {5, 10, 15, 30, 30, 30}; + for (long backoff : backoffSeconds) { + List rows = + Lists.newArrayList( + dataClient.readRows(Query.create(MaterializedViewId.of(materializedViewId)))); + if (!rows.isEmpty()) { + row = rows.get(0); + break; + } + Thread.sleep(backoff * 1000); + } + + assertThat(row).isNotNull(); + assertThat(row.getKey()).isEqualTo(ByteString.copyFromUtf8(rowKey)); + } finally { + instanceAdmin.deleteMaterializedView(instanceId, materializedViewId); + } + } finally { + tableAdmin.deleteTable(tableId); + } + } + @Test public void read() throws Throwable { int numRows = 5; diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java index 146cc1017fe0..788ad401e915 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java @@ -47,6 +47,7 @@ import com.google.cloud.bigtable.data.v2.models.KeyOffset; import com.google.cloud.bigtable.data.v2.models.Mutation; import com.google.cloud.bigtable.data.v2.models.Query; +import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow; import com.google.cloud.bigtable.data.v2.models.Row; import com.google.cloud.bigtable.data.v2.models.RowMutation; @@ -135,6 +136,13 @@ public void readRowsTest() { verifyHeaderSent(); } + @Test + public void readRowsMaterializedViewTest() { + // Materialized views are instance-scoped, so routing happens on the instance name. + client.readRows(Query.create(MaterializedViewId.of("fake-materialized-view"))); + verifyHeaderSent(true); + } + @Test public void sampleRowKeysTest() { @SuppressWarnings("UnusedVariable") @@ -142,6 +150,15 @@ public void sampleRowKeysTest() { verifyHeaderSent(); } + @Test + public void sampleRowKeysMaterializedViewTest() { + // Materialized views are instance-scoped, so routing happens on the instance name. + @SuppressWarnings("UnusedVariable") + ApiFuture> ignored = + client.sampleRowKeysAsync(MaterializedViewId.of("fake-materialized-view")); + verifyHeaderSent(true); + } + @Test public void mutateRowTest() { ApiFuture ignored = From eda51af28ffb7e16f3b57a401ae26445ae064f6d Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 27 Jul 2026 22:13:31 -0400 Subject: [PATCH 2/2] reformat --- .../cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java | 8 ++------ .../java/com/google/cloud/bigtable/data/v2/it/ReadIT.java | 4 +--- .../google/cloud/bigtable/data/v2/stub/HeadersTest.java | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index 91cd35c34bcb..1f1a45c46b05 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -377,9 +377,7 @@ private ServerStreamingCallable createReadRo r -> r.getMaterializedViewName().isEmpty() ? composeRequestParams( - r.getAppProfileId(), - r.getTableName(), - r.getAuthorizedViewName()) + r.getAppProfileId(), r.getTableName(), r.getAuthorizedViewName()) : composeInstanceLevelRequestParams( MaterializedViewName.parse(r.getMaterializedViewName()) .getInstanceName() @@ -459,9 +457,7 @@ public ServerStreamingCallable createSkipLargeRowsCall r -> r.getMaterializedViewName().isEmpty() ? composeRequestParams( - r.getAppProfileId(), - r.getTableName(), - r.getAuthorizedViewName()) + r.getAppProfileId(), r.getTableName(), r.getAuthorizedViewName()) : composeInstanceLevelRequestParams( MaterializedViewName.parse(r.getMaterializedViewName()) .getInstanceName() diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java index 10160d299e0a..e5af176e2172 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java @@ -249,9 +249,7 @@ public void readFromMaterializedView() throws Throwable { instanceAdmin.createMaterializedView( CreateMaterializedViewRequest.of(instanceId, materializedViewId) .setQuery( - "SELECT _key, MAX(cf1['column']) as column FROM `" - + tableId - + "` GROUP BY _key") + "SELECT _key, MAX(cf1['column']) as column FROM `" + tableId + "` GROUP BY _key") .setDeletionProtection(false)); try { Row row = null; diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java index 788ad401e915..dcf22e1e19d4 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/HeadersTest.java @@ -45,9 +45,9 @@ import com.google.cloud.bigtable.data.v2.FakeServiceBuilder; import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation; import com.google.cloud.bigtable.data.v2.models.KeyOffset; +import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.Mutation; import com.google.cloud.bigtable.data.v2.models.Query; -import com.google.cloud.bigtable.data.v2.models.MaterializedViewId; import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow; import com.google.cloud.bigtable.data.v2.models.Row; import com.google.cloud.bigtable.data.v2.models.RowMutation;