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..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 @@ -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,14 @@ 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 +455,14 @@ 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 +611,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..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 @@ -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,56 @@ 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..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,6 +45,7 @@ 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.ReadModifyWriteRow; @@ -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 =