From d141c3d597cbd682050b78bb3828fd4d8c96a7c3 Mon Sep 17 00:00:00 2001 From: Rahul Kesharwani <42969463+rahulKQL@users.noreply.github.com> Date: Thu, 6 Feb 2020 03:52:11 +0530 Subject: [PATCH] feat: add row exists api (#190) * feat: add row exists api Adding a new Row exists() API in `BigtableDataClient` * rephrased java doc * addressed feedback comment to add FILTERS.value().strip() --- .../bigtable/data/v2/BigtableDataClient.java | 134 ++++++++++++++++++ .../data/v2/BigtableDataClientTest.java | 51 +++++++ .../cloud/bigtable/data/v2/it/ReadIT.java | 20 +++ 3 files changed, 205 insertions(+) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java index 3b287594f..40d9f70ca 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java @@ -15,7 +15,11 @@ */ package com.google.cloud.bigtable.data.v2; +import static com.google.cloud.bigtable.data.v2.models.Filters.FILTERS; + +import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.batching.Batcher; @@ -36,6 +40,7 @@ import com.google.cloud.bigtable.data.v2.models.RowMutation; import com.google.cloud.bigtable.data.v2.models.RowMutationEntry; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import java.io.IOException; import java.util.List; @@ -159,6 +164,135 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO this.stub = stub; } + /** + * Confirms synchronously if given row key exists or not. + * + *

Sample code: + * + *

{@code
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create("[PROJECT]", "[INSTANCE]")) {
+   *   String tableId = "[TABLE]";
+   *   String key = "key";
+   *
+   *   boolean isRowPresent = bigtableDataClient.exists(tableId, key);
+   *
+   *   // Do something with result, for example, display a message
+   *   if(isRowPresent) {
+   *     System.out.println(key + " is present");
+   *   }
+   * } catch(ApiException e) {
+   *   e.printStackTrace();
+   * }
+   * }
+ * + * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs + */ + public boolean exists(String tableId, String rowKey) { + return ApiExceptions.callAndTranslateApiException(existsAsync(tableId, rowKey)); + } + + /** + * Confirms synchronously if given row key exists or not. + * + *

Sample code: + * + *

{@code
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create("[PROJECT]", "[INSTANCE]")) {
+   *   String tableId = "[TABLE]";
+   *   ByteString key = ByteString.copyFromUtf8("key");
+   *
+   *   boolean isRowPresent = bigtableDataClient.exists(tableId, key);
+   *
+   *   // Do something with result, for example, display a message
+   *   if(isRowPresent) {
+   *     System.out.println(key.toStringUtf8() + " is present");
+   *   }
+   * } catch(ApiException e) {
+   *   e.printStackTrace();
+   * }
+   * }
+ * + * @throws com.google.api.gax.rpc.ApiException when a serverside error occurs + */ + public boolean exists(String tableId, ByteString rowKey) { + return ApiExceptions.callAndTranslateApiException(existsAsync(tableId, rowKey)); + } + + /** + * Confirms asynchronously if given row key exists or not. + * + *

Sample code: + * + *

{@code
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create("[PROJECT]", "[INSTANCE]")) {
+   *   String tableId = "[TABLE]";
+   *   final String key = "key";
+   *
+   *   ApiFuture futureResult = bigtableDataClient.existsAsync(tableId, key);
+   *
+   *   ApiFutures.addCallback(futureResult, new ApiFutureCallback() {
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *     public void onSuccess(Boolean isRowPresent) {
+   *       if(isRowPresent) {
+   *         System.out.println(key + " is present");
+   *       }
+   *     }
+   *   }, MoreExecutors.directExecutor());
+   * }
+   * }
+ */ + public ApiFuture existsAsync(String tableId, String rowKey) { + return existsAsync(tableId, ByteString.copyFromUtf8(rowKey)); + } + + /** + * Confirms asynchronously if given row key exists or not. + * + *

Sample code: + * + *

{@code
+   * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create("[PROJECT]", "[INSTANCE]")) {
+   *   String tableId = "[TABLE]";
+   *   final ByteString key = ByteString.copyFromUtf8("key");
+   *
+   *   ApiFuture futureResult = bigtableDataClient.existsAsync(tableId, key);
+   *
+   *   ApiFutures.addCallback(futureResult, new ApiFutureCallback() {
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *     public void onSuccess(Boolean isRowPresent) {
+   *       if(isRowPresent) {
+   *         System.out.println(key.toStringUtf8() + " is present");
+   *       }
+   *     }
+   *   }, MoreExecutors.directExecutor());
+   * }
+   * }
+ */ + public ApiFuture existsAsync(String tableId, ByteString rowKey) { + Query query = + Query.create(tableId) + .rowKey(rowKey) + .filter( + FILTERS + .chain() + .filter(FILTERS.limit().cellsPerRow(1)) + .filter(FILTERS.value().strip())); + ApiFuture resultFuture = stub.readRowCallable().futureCall(query); + return ApiFutures.transform( + resultFuture, + new ApiFunction() { + @Override + public Boolean apply(Row row) { + return row != null; + } + }, + MoreExecutors.directExecutor()); + } + /** * Convenience method for synchronously reading a single row. If the row does not exist, the value * will be null. diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java index b83800c56..c3bf52b63 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientTest.java @@ -92,6 +92,57 @@ public void proxyCloseTest() throws Exception { Mockito.verify(mockStub).close(); } + @Test + public void existsTest() { + Query expectedQuery = + Query.create("fake-table") + .rowKey("fake-row-key") + .filter( + FILTERS + .chain() + .filter(FILTERS.limit().cellsPerRow(1)) + .filter(FILTERS.value().strip())); + Row row = Row.create(ByteString.copyFromUtf8("fake-row-key"), ImmutableList.of()); + Mockito.when(mockReadRowCallable.futureCall(expectedQuery)) + .thenReturn(ApiFutures.immediateFuture(row)) + .thenReturn(ApiFutures.immediateFuture(null)); + + boolean result = bigtableDataClient.exists("fake-table", "fake-row-key"); + boolean anotherResult = + bigtableDataClient.exists("fake-table", ByteString.copyFromUtf8("fake-row-key")); + + assertThat(result).isTrue(); + assertThat(anotherResult).isFalse(); + + Mockito.verify(mockReadRowCallable, Mockito.times(2)).futureCall(expectedQuery); + } + + @Test + public void existsAsyncTest() throws Exception { + Query expectedQuery = + Query.create("fake-table") + .rowKey("fake-row-key") + .filter( + FILTERS + .chain() + .filter(FILTERS.limit().cellsPerRow(1)) + .filter(FILTERS.value().strip())); + Row row = Row.create(ByteString.copyFromUtf8("fake-row-key"), ImmutableList.of()); + + Mockito.when(mockReadRowCallable.futureCall(expectedQuery)) + .thenReturn(ApiFutures.immediateFuture(row)) + .thenReturn(ApiFutures.immediateFuture(null)); + + ApiFuture result = + bigtableDataClient.existsAsync("fake-table", ByteString.copyFromUtf8("fake-row-key")); + assertThat(result.get()).isTrue(); + + ApiFuture anotherResult = bigtableDataClient.existsAsync("fake-table", "fake-row-key"); + assertThat(anotherResult.get()).isFalse(); + + Mockito.verify(mockReadRowCallable, Mockito.times(2)).futureCall(expectedQuery); + } + @Test public void proxyReadRowsCallableTest() { assertThat(bigtableDataClient.readRowsCallable()).isSameInstanceAs(mockReadRowsCallable); diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java index 0e4a5f37a..4d7fdcdb1 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/ReadIT.java @@ -57,6 +57,26 @@ public void setUp() { prefix = UUID.randomUUID().toString(); } + @Test + public void isRowExists() throws Exception { + String rowKey = prefix + "-test-row-key"; + String tableId = testEnvRule.env().getTableId(); + testEnvRule + .env() + .getDataClient() + .mutateRow( + RowMutation.create(tableId, rowKey) + .setCell(testEnvRule.env().getFamilyId(), "qualifier", "value")); + + assertThat(testEnvRule.env().getDataClient().exists(tableId, rowKey)).isTrue(); + + String nonExistingKey = prefix + "non-existing-key"; + assertThat(testEnvRule.env().getDataClient().exists(tableId, nonExistingKey)).isFalse(); + + // Async + assertThat(testEnvRule.env().getDataClient().existsAsync(tableId, rowKey).get()).isTrue(); + } + @Test public void readEmpty() throws Throwable { String uniqueKey = prefix + "-readEmpty";