diff --git a/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java b/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java index 6e9d085178b7..d1061ac74d11 100644 --- a/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java +++ b/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java @@ -26,6 +26,7 @@ import com.google.cloud.bigtable.data.v2.models.BulkMutation; import com.google.cloud.bigtable.data.v2.models.BulkMutationBatcher; import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation; +import com.google.cloud.bigtable.data.v2.models.Filters.Filter; import com.google.cloud.bigtable.data.v2.models.InstanceName; import com.google.cloud.bigtable.data.v2.models.KeyOffset; import com.google.cloud.bigtable.data.v2.models.Query; @@ -37,6 +38,7 @@ import com.google.protobuf.ByteString; import java.io.IOException; import java.util.List; +import javax.annotation.Nullable; /** * Client for reading from and writing to existing Bigtable tables. @@ -143,7 +145,7 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO * *

Sample code: * - *

{code
+   * 
{@code
    * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
    * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
    *   String tableId = "[TABLE]";
@@ -153,7 +155,8 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO
    *   if(row != null) {
    *     System.out.println(row.getKey().toStringUtf8());
    *     for(RowCell cell : row.getCells()) {
-   *       System.out.println("Family: " + cell.getFamily() + "   Qualifier: " + cell.getQualifier().toStringUtf8() + "   Value: " + cell.getValue().toStringUtf8());
+   *        System.out.printf("Family: %s   Qualifier: %s   Value: %s", cell.getFamily(),
+   *           cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
    *     }
    *   }
    * } catch(ApiException e) {
@@ -164,7 +167,7 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO
    * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs
    */
   public Row readRow(String tableId, ByteString rowKey) {
-    return ApiExceptions.callAndTranslateApiException(readRowAsync(tableId, rowKey));
+    return ApiExceptions.callAndTranslateApiException(readRowAsync(tableId, rowKey, null));
   }
 
   /**
@@ -173,7 +176,7 @@ public Row readRow(String tableId, ByteString rowKey) {
    *
    * 

Sample code: * - *

{code
+   * 
{@code
    * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
    * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
    *   String tableId = "[TABLE]";
@@ -183,7 +186,8 @@ public Row readRow(String tableId, ByteString rowKey) {
    *   if(row != null) {
    *     System.out.println(row.getKey().toStringUtf8());
    *      for(RowCell cell : row.getCells()) {
-   *        System.out.println("Family: " + cell.getFamily() + "   Qualifier: " + cell.getQualifier().toStringUtf8() + "   Value: " + cell.getValue().toStringUtf8());
+   *        System.out.printf("Family: %s   Qualifier: %s   Value: %s", cell.getFamily(),
+   *           cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
    *      }
    *   }
    * } catch(ApiException e) {
@@ -194,7 +198,81 @@ public Row readRow(String tableId, ByteString rowKey) {
    * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs
    */
   public Row readRow(String tableId, String rowKey) {
-    return ApiExceptions.callAndTranslateApiException(readRowAsync(tableId, rowKey));
+    return ApiExceptions.callAndTranslateApiException(
+        readRowAsync(tableId, ByteString.copyFromUtf8(rowKey), null));
+  }
+
+  /**
+   * Convenience method for synchronously reading a single row. If the row does not exist, the value
+   * will be null.
+   *
+   * 

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *  // Build the filter expression
+   *  Filter filter = FILTERS.chain()
+   *         .filter(FILTERS.qualifier().regex("prefix.*"))
+   *         .filter(FILTERS.limit().cellsPerRow(10));
+   *
+   *   Row row = bigtableDataClient.readRow(tableId, "key", filter);
+   *   // Do something with row, for example, display all cells
+   *   if(row != null) {
+   *     System.out.println(row.getKey().toStringUtf8());
+   *      for(RowCell cell : row.getCells()) {
+   *        System.out.printf("Family: %s   Qualifier: %s   Value: %s", cell.getFamily(),
+   *           cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
+   *      }
+   *   }
+   * } catch(ApiException e) {
+   *   e.printStackTrace();
+   * }
+   * }
+ * + * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs + */ + public Row readRow(String tableId, String rowKey, @Nullable Filter filter) { + return ApiExceptions.callAndTranslateApiException( + readRowAsync(tableId, ByteString.copyFromUtf8(rowKey), filter)); + } + + /** + * Convenience method for synchronously reading a single row. If the row does not exist, the value + * will be null. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *  // Build the filter expression
+   *  Filter filter = FILTERS.chain()
+   *         .filter(FILTERS.qualifier().regex("prefix.*"))
+   *         .filter(FILTERS.limit().cellsPerRow(10));
+   *
+   *   Row row = bigtableDataClient.readRow(tableId, ByteString.copyFromUtf8("key"), filter);
+   *   // Do something with row, for example, display all cells
+   *   if(row != null) {
+   *     System.out.println(row.getKey().toStringUtf8());
+   *      for(RowCell cell : row.getCells()) {
+   *        System.out.printf("Family: %s   Qualifier: %s   Value: %s", cell.getFamily(),
+   *           cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
+   *      }
+   *   }
+   * } catch(ApiException e) {
+   *   e.printStackTrace();
+   * }
+   * }
+ * + * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs + */ + public Row readRow(String tableId, ByteString rowKey, @Nullable Filter filter) { + return ApiExceptions.callAndTranslateApiException(readRowAsync(tableId, rowKey, filter)); } /** @@ -228,7 +306,7 @@ public Row readRow(String tableId, String rowKey) { * }
*/ public ApiFuture readRowAsync(String tableId, String rowKey) { - return readRowAsync(tableId, ByteString.copyFromUtf8(rowKey)); + return readRowAsync(tableId, ByteString.copyFromUtf8(rowKey), null); } /** @@ -262,11 +340,93 @@ public ApiFuture readRowAsync(String tableId, String rowKey) { * }
*/ public ApiFuture readRowAsync(String tableId, ByteString rowKey) { - return readRowsCallable().first().futureCall(Query.create(tableId).rowKey(rowKey)); + return readRowAsync(tableId, rowKey, null); + } + + /** + * Convenience method for asynchronously reading a single row. If the row does not exist, the + * future's value will be null. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *  // Build the filter expression
+   *   Filters.Filter filter = FILTERS.chain()
+   *         .filter(FILTERS.qualifier().regex("prefix.*"))
+   *         .filter(FILTERS.limit().cellsPerRow(10));
+   *
+   *   ApiFuture futureResult = bigtableDataClient.readRowAsync(tableId, "key", filter);
+   *
+   *   ApiFutures.addCallback(futureResult, new ApiFutureCallback() {
+   *     public void onFailure(Throwable t) {
+   *       if (t instanceof NotFoundException) {
+   *         System.out.println("Tried to read a non-existent table");
+   *       } else {
+   *         t.printStackTrace();
+   *       }
+   *     }
+   *     public void onSuccess(Row row) {
+   *       if (result != null) {
+   *          System.out.println("Got row: " + result);
+   *       }
+   *     }
+   *   }, MoreExecutors.directExecutor());
+   * }
+   * }
+ */ + public ApiFuture readRowAsync(String tableId, String rowKey, @Nullable Filter filter) { + return readRowAsync(tableId, ByteString.copyFromUtf8(rowKey), filter); + } + + /** + * Convenience method for asynchronously reading a single row. If the row does not exist, the + * future's value will be null. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *  // Build the filter expression
+   *  Filters.Filter filter = FILTERS.chain()
+   *         .filter(FILTERS.qualifier().regex("prefix.*"))
+   *         .filter(FILTERS.limit().cellsPerRow(10));
+   *
+   *   ApiFuture futureResult = bigtableDataClient.readRowAsync(tableId, ByteString.copyFromUtf8("key"), filter);
+   *
+   *   ApiFutures.addCallback(futureResult, new ApiFutureCallback() {
+   *     public void onFailure(Throwable t) {
+   *       if (t instanceof NotFoundException) {
+   *         System.out.println("Tried to read a non-existent table");
+   *       } else {
+   *         t.printStackTrace();
+   *       }
+   *     }
+   *     public void onSuccess(Row row) {
+   *       if (result != null) {
+   *          System.out.println("Got row: " + result);
+   *       }
+   *     }
+   *   }, MoreExecutors.directExecutor());
+   * }
+   * }
+ */ + public ApiFuture readRowAsync(String tableId, ByteString rowKey, @Nullable Filter filter) { + Query query = Query.create(tableId).rowKey(rowKey); + if (filter != null) { + query = query.filter(filter); + } + return readRowsCallable().first().futureCall(query); } /** - * Convenience method for synchronous streaming the results of a {@link Query}. + * Convenience method for synchronously streaming the results of a {@link Query}. * *

Sample code: * @@ -304,7 +464,7 @@ public ServerStream readRows(Query query) { } /** - * Convenience method for asynchronous streaming the results of a {@link Query}. + * Convenience method for asynchronously streaming the results of a {@link Query}. * *

Sample code: * diff --git a/google-cloud-clients/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java b/google-cloud-clients/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java index 622c8f308263..be6cb2b965c5 100644 --- a/google-cloud-clients/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java +++ b/google-cloud-clients/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java @@ -15,6 +15,7 @@ */ package com.google.cloud.bigtable.data.v2; +import static com.google.cloud.bigtable.data.v2.models.Filters.FILTERS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; @@ -33,6 +34,7 @@ import com.google.cloud.bigtable.data.v2.models.BulkMutationBatcher; import com.google.cloud.bigtable.data.v2.models.BulkMutationBatcher.BulkMutationFailure; import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation; +import com.google.cloud.bigtable.data.v2.models.Filters.Filter; import com.google.cloud.bigtable.data.v2.models.InstanceName; import com.google.cloud.bigtable.data.v2.models.KeyOffset; import com.google.cloud.bigtable.data.v2.models.Mutation; @@ -137,6 +139,66 @@ public void proxyReadRowStrAsyncTest() { .build()); } + @Test + public void readRowFilterAsyncTest() { + // Build the filter expression + Filter filter = + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)); + bigtableDataClient.readRowAsync("fake-table", ByteString.copyFromUtf8("fake-row-key"), filter); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Query.class); + Mockito.verify(mockReadRowsCallable.first()).futureCall(requestCaptor.capture()); + + RequestContext ctx = + RequestContext.create(InstanceName.of("fake-project", "fake-instance"), "fake-profile"); + // NOTE: limit(1) is added by the mocked first() call, so it's not tested here + assertThat(requestCaptor.getValue().toProto(ctx)) + .isEqualTo( + ReadRowsRequest.newBuilder() + .setTableName("projects/fake-project/instances/fake-instance/tables/fake-table") + .setAppProfileId("fake-profile") + .setRows(RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("fake-row-key"))) + .setFilter( + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)) + .toProto()) + .build()); + } + + @Test + public void readRowFilterStrAsyncTest() { + // Build the filter expression + Filter filter = + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)); + bigtableDataClient.readRowAsync("fake-table", "fake-row-key", filter); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Query.class); + Mockito.verify(mockReadRowsCallable.first()).futureCall(requestCaptor.capture()); + + RequestContext ctx = + RequestContext.create(InstanceName.of("fake-project", "fake-instance"), "fake-profile"); + // NOTE: limit(1) is added by the mocked first() call, so it's not tested here + assertThat(requestCaptor.getValue().toProto(ctx)) + .isEqualTo( + ReadRowsRequest.newBuilder() + .setTableName("projects/fake-project/instances/fake-instance/tables/fake-table") + .setAppProfileId("fake-profile") + .setRows(RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("fake-row-key"))) + .setFilter( + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)) + .toProto()) + .build()); + } + @Test public void readRowTest() { Mockito.when(mockReadRowsCallable.first().futureCall(any(Query.class))) @@ -185,6 +247,78 @@ public void readRowStrTest() { .build()); } + @Test + public void readRowFilterTest() { + // Build the filter expression + Filter filter = + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)); + Mockito.when(mockReadRowsCallable.first().futureCall(any(Query.class))) + .thenReturn( + ApiFutures.immediateFuture( + Row.create( + ByteString.copyFromUtf8("fake-row-key"), Collections.emptyList()))); + bigtableDataClient.readRow("fake-table", ByteString.copyFromUtf8("fake-row-key"), filter); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Query.class); + Mockito.verify(mockReadRowsCallable.first()).futureCall(requestCaptor.capture()); + + RequestContext ctx = + RequestContext.create(InstanceName.of("fake-project", "fake-instance"), "fake-profile"); + // NOTE: limit(1) is added by the mocked first() call, so it's not tested here + assertThat(requestCaptor.getValue().toProto(ctx)) + .isEqualTo( + ReadRowsRequest.newBuilder() + .setTableName("projects/fake-project/instances/fake-instance/tables/fake-table") + .setAppProfileId("fake-profile") + .setRows(RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("fake-row-key"))) + .setFilter( + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)) + .toProto()) + .build()); + } + + @Test + public void readRowStrFilterTest() { + // Build the filter expression + Filter filter = + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)); + Mockito.when(mockReadRowsCallable.first().futureCall(any(Query.class))) + .thenReturn( + ApiFutures.immediateFuture( + Row.create( + ByteString.copyFromUtf8("fake-row-key"), Collections.emptyList()))); + bigtableDataClient.readRow("fake-table", "fake-row-key", filter); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Query.class); + Mockito.verify(mockReadRowsCallable.first()).futureCall(requestCaptor.capture()); + + RequestContext ctx = + RequestContext.create(InstanceName.of("fake-project", "fake-instance"), "fake-profile"); + // NOTE: limit(1) is added by the mocked first() call, so it's not tested here + assertThat(requestCaptor.getValue().toProto(ctx)) + .isEqualTo( + ReadRowsRequest.newBuilder() + .setTableName("projects/fake-project/instances/fake-instance/tables/fake-table") + .setAppProfileId("fake-profile") + .setRows(RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("fake-row-key"))) + .setFilter( + FILTERS + .chain() + .filter(FILTERS.qualifier().regex("prefix.*")) + .filter(FILTERS.limit().cellsPerRow(10)) + .toProto()) + .build()); + } + @Test public void proxyReadRowsSyncTest() { Query query = Query.create("fake-table");