From f374e663c9dd021019e3428b6be80391b55c2e04 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Fri, 2 Feb 2018 22:32:18 -0500 Subject: [PATCH 01/18] 02. Surface: ReadRows Adding the user facing api for ReadRows: - Adds wrappers for the ReadRowsRequest and Response - Introduces a fluent DSL for filters - Introduces the custom RowAdapter api where the user can customize the logical rows without introducing extra copies --- .../bigtable/data/v2/BigtableDataClient.java | 152 +++- .../data/v2/BigtableDataSettings.java | 22 +- .../bigtable/data/v2/internal/RegexUtil.java | 79 ++ .../data/v2/stub/EnhancedBigtableStub.java | 24 + .../v2/stub/EnhancedBigtableStubSettings.java | 50 +- .../data/v2/wrappers/DefaultRowAdapter.java | 109 +++ .../bigtable/data/v2/wrappers/Filters.java | 795 ++++++++++++++++++ .../bigtable/data/v2/wrappers/Query.java | 140 +++ .../bigtable/data/v2/wrappers/Range.java | 81 ++ .../cloud/bigtable/data/v2/wrappers/Row.java | 102 +++ .../bigtable/data/v2/wrappers/RowAdapter.java | 81 ++ ...laceholderServerStreamingCallSettings.java | 165 ++++ .../data/v2/BigtableDataClientTest.java | 30 + .../data/v2/internal/RegexUtilTest.java | 50 ++ .../EnhancedBigtableStubSettingsTest.java | 83 ++ .../v2/wrappers/DefaultRowAdapterTest.java | 132 +++ .../data/v2/wrappers/FiltersTest.java | 399 +++++++++ .../bigtable/data/v2/wrappers/QueryTest.java | 128 +++ .../bigtable/data/v2/wrappers/RangeTest.java | 51 ++ .../bigtable/data/v2/wrappers/RowTest.java | 69 ++ 20 files changed, 2730 insertions(+), 12 deletions(-) create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java 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 b003190a2271..5a4bb8f08f84 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 @@ -16,8 +16,14 @@ package com.google.cloud.bigtable.data.v2; import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.ServerStream; +import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.cloud.bigtable.data.v2.wrappers.RowAdapter; import java.io.IOException; /** @@ -29,7 +35,9 @@ *
{@code
  * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
  * try (BigtableDataClient bigtableDataClient = BigtableDataClient.create(instanceName)) {
- *   // TODO: add example usage
+ *   for(Row row : bigtableDataClient.readRows(Query.create("[TABLE]")) {
+ *     // Do something with row
+ *   }
  * }
  * }
* @@ -40,11 +48,13 @@ * methods: * *
    - *
  1. A "flattened" method. With this type of method, the fields of the request type have been - * converted into function parameters. It may be the case that not all fields are available as - * parameters, and not every API method will have a flattened method entry point. - *
  2. A "callable" method. This type of method takes no parameters and returns an immutable API - * callable object, which can be used to initiate calls to the service. + *
  3. A "flattened" method, like `readRows()`. With this type of method, the fields of the + * request type have been converted into function parameters. It may be the case that not all + * fields are available as parameters, and not every API method will have a flattened method + * entry point. + *
  4. A "callable" method, like `readRowsCallable()`. This type of method takes no parameters and + * returns an immutable API callable object, which can be used to initiate calls to the + * service. *
* *

See the individual methods for example code. @@ -112,4 +122,134 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO public void close() throws Exception { stub.close(); } + + /** + * Convenience method for synchronous streaming the results of a {@link Query}. + * + *

Sample code: + * + *

{@code
+   * // Import the filter DSL
+   * import static com.google.cloud.bigtable.data.v2.wrappers.Filters.FILTERS;
+   *
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableClient bigtableClient = BigtableClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *   Query query = Query.create(tableId)
+   *          .range("[START KEY]", "[END KEY]")
+   *          .filter(FILTERS.qualifier().regex("[COLUMN PREFIX].*"));
+   *
+   *   // Iterator style
+   *   for(Row row : bigtableClient.readRows(query)) {
+   *     // Do something with row
+   *   }
+   * }
+   * }
+ * + * @see ServerStreamingCallable For call styles. + * @see Query For query options. + * @see com.google.cloud.bigtable.data.v2.wrappers.Filters For the filter building DSL. + */ + public ServerStream readRows(Query query) { + return readRowsCallable().call(query); + } + + /** + * Convenience method for asynchronous streaming the results of a {@link Query}. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableClient bigtableClient = BigtableClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *   Query query = Query.create(tableId)
+   *          .range("[START KEY]", "[END KEY]")
+   *          .filter(FILTERS.qualifier().regex("[COLUMN PREFIX].*"));
+   *
+   *   client.readRowsAsync(query, new ResponseObserver() {
+   *     public void onStart(StreamController controller) { }
+   *     public void onResponse(Row response) {
+   *       // Do something with Row
+   *     }
+   *     public void onError(Throwable t) {
+   *       // Handle error before the stream completes
+   *     }
+   *     public void onComplete() {
+   *       // Handle stream completion
+   *     }
+   *   });
+   * }
+   * }
+ */ + public void readRowsAsync(Query query, ResponseObserver observer) { + readRowsCallable().call(query, observer); + } + + /** + * Streams back the results of the query. The returned callable object allows for customization of + * api invocation. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableClient bigtableClient = BigtableClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *   Query query = Query.create(tableId)
+   *          .range("[START KEY]", "[END KEY]")
+   *          .filter(FILTERS.qualifier().regex("[COLUMN PREFIX].*"));
+   *
+   *   // Iterator style
+   *   for(Row row : bigtableClient.readRowsCallable().call(query)) {
+   *     // Do something with row
+   *   }
+   *
+   *   // Point look ups
+   *   ApiFuture rowFuture = bigtableClient.readRowsCallable().first().futureCall(query);
+   *
+   *   // etc
+   * }
+   * }
+ * + * @see ServerStreamingCallable For call styles. + * @see Query For query options. + * @see com.google.cloud.bigtable.data.v2.wrappers.Filters For the filter building DSL. + */ + public ServerStreamingCallable readRowsCallable() { + return stub.readRowsCallable(); + } + + /** + * Streams back the results of the query. This callable allows for customization of the logical + * representation of a row. It's meant for advanced use cases. + * + *

Sample code: + * + *

{@code
+   * InstanceName instanceName = InstanceName.of("[PROJECT]", "[INSTANCE]");
+   * try (BigtableClient bigtableClient = BigtableClient.create(instanceName)) {
+   *   String tableId = "[TABLE]";
+   *
+   *   Query query = Query.create(tableId)
+   *          .range("[START KEY]", "[END KEY]")
+   *          .filter(FILTERS.qualifier().regex("[COLUMN PREFIX].*"));
+   *
+   *   // Iterator style
+   *   for(CustomRow row : bigtableClient.readRowsCallable(new CustomRowAdapter()).call(query)) {
+   *     // Do something with row
+   *   }
+   * }
+   * }
+ * + * @see ServerStreamingCallable For call styles. + * @see Query For query options. + * @see com.google.cloud.bigtable.data.v2.wrappers.Filters For the filter building DSL. + */ + public ServerStreamingCallable readRowsCallable(RowAdapter rowAdapter) { + return stub.createReadRowsCallable(rowAdapter); + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java index 9889b8061605..aa59eb8e8982 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java @@ -18,6 +18,9 @@ import com.google.api.gax.rpc.ClientSettings; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; import java.io.IOException; import javax.annotation.Nonnull; @@ -38,10 +41,13 @@ * build() is called, the tree of builders is called to create the complete settings object. * *
{@code
- * BigtableDataSettings bigtableDataSettings = BigtableDataSettings.newBuilder()
+ * BigtableDataSettings.Builder settingsBuilder = BigtableDataSettings.newBuilder()
  *   .setInstanceName(InstanceName.of("my-project", "my-instance-id"))
- *   .setAppProfileId("default")
- *   .build();
+ *   .setAppProfileId("default");
+ *
+ * settingsBuilder.readRowsSettings().setRetryableCodes(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE);
+ *
+ * BigtableDataSettings settings = builder.build();
  * }
*/ public class BigtableDataSettings extends ClientSettings { @@ -64,6 +70,11 @@ public String getAppProfileId() { return getTypedStubSettings().getAppProfileId(); } + /** Returns the object with the settings used for calls to ReadRows. */ + public PlaceholderServerStreamingCallSettings readRowsSettings() { + return getTypedStubSettings().readRowsSettings(); + } + @SuppressWarnings("unchecked") EnhancedBigtableStubSettings getTypedStubSettings() { return (EnhancedBigtableStubSettings) getStubSettings(); @@ -124,6 +135,11 @@ public String getAppProfileId() { return getTypedStubSettings().getAppProfileId(); } + /** Returns the builder for the settings used for calls to readRows. */ + public PlaceholderServerStreamingCallSettings.Builder readRowsSettings() { + return getTypedStubSettings().readRowsSettings(); + } + @SuppressWarnings("unchecked") private EnhancedBigtableStubSettings.Builder getTypedStubSettings() { return (EnhancedBigtableStubSettings.Builder) getStubSettings(); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java new file mode 100644 index 000000000000..0c9b147d6c38 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.internal; + +import com.google.api.core.InternalApi; +import com.google.protobuf.ByteString; +import com.google.protobuf.ByteString.ByteIterator; +import com.google.protobuf.ByteString.Output; +import java.io.IOException; +import java.io.OutputStream; + +@InternalApi +public final class RegexUtil { + private static final byte[] NULL_BYTES = "\\x00".getBytes(); + + private RegexUtil() {} + + public static String literalRegex(final String value) { + return literalRegex(ByteString.copyFromUtf8(value)).toStringUtf8(); + } + /** Converts the value to a quoted regular expression. */ + public static ByteString literalRegex(ByteString value) { + Output output = ByteString.newOutput(value.size() * 2); + + ByteIterator it = value.iterator(); + try { + writeLiteralRegex(it, output); + } catch (IOException e) { + throw new RuntimeException("Unexpected io error converting regex", e); + } + + return output.toByteString(); + } + + // Extracted from: re2 QuoteMeta: + // https://github.com/google/re2/blob/70f66454c255080a54a8da806c52d1f618707f8a/re2/re2.cc#L456 + private static void writeLiteralRegex(ByteIterator input, OutputStream output) + throws IOException { + while (input.hasNext()) { + byte unquoted = input.nextByte(); + + if ((unquoted < 'a' || unquoted > 'z') + && (unquoted < 'A' || unquoted > 'Z') + && (unquoted < '0' || unquoted > '9') + && unquoted != '_' + && + // If this is the part of a UTF8 or Latin1 character, we need + // to copy this byte without escaping. Experimentally this is + // what works correctly with the regexp library. + (unquoted & 128) == 0) { + + if (unquoted == '\0') { // Special handling for null chars. + // Note that this special handling is not strictly required for RE2, + // but this quoting is required for other regexp libraries such as + // PCRE. + // Can't use "\\0" since the next character might be a digit. + output.write(NULL_BYTES, 0, NULL_BYTES.length); + continue; + } + + output.write('\\'); + } + output.write(unquoted); + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index 0acd0b53ca2b..dc49fc4f9e4b 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -16,8 +16,15 @@ package com.google.cloud.bigtable.data.v2.stub; import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.cloud.bigtable.data.v2.internal.RequestContext; +import com.google.cloud.bigtable.data.v2.wrappers.DefaultRowAdapter; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.cloud.bigtable.data.v2.wrappers.RowAdapter; import java.io.IOException; /** @@ -39,6 +46,8 @@ public class EnhancedBigtableStub implements AutoCloseable { private final ClientContext clientContext; private final RequestContext requestContext; + private final ServerStreamingCallable readRowsCallable; + public static EnhancedBigtableStub create(EnhancedBigtableStubSettings settings) throws IOException { // Configure the base settings @@ -63,6 +72,8 @@ public static EnhancedBigtableStub create(EnhancedBigtableStubSettings settings) this.stub = stub; this.requestContext = RequestContext.create(settings.getInstanceName(), settings.getAppProfileId()); + + readRowsCallable = createReadRowsCallable(new DefaultRowAdapter()); } @Override @@ -71,8 +82,21 @@ public void close() throws Exception { } // + public ServerStreamingCallable createReadRowsCallable( + RowAdapter rowAdapter) { + return new ServerStreamingCallable() { + @Override + public void call( + Query query, ResponseObserver responseObserver, ApiCallContext context) { + throw new UnsupportedOperationException("todo"); + } + }; + } // // + public ServerStreamingCallable readRowsCallable() { + return readRowsCallable; + } // } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index 8efa594a0d5c..e69d8766d82d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -17,11 +17,17 @@ import com.google.api.core.InternalApi; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.StubSettings; import com.google.api.gax.rpc.UnaryCallSettings; import com.google.bigtable.admin.v2.InstanceName; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; import com.google.common.base.Preconditions; import javax.annotation.Nonnull; +import org.threeten.bp.Duration; /** * Settings class to configure an instance of {@link EnhancedBigtableStub}. @@ -40,10 +46,14 @@ * build() is called, the tree of builders is called to create the complete settings object. * *
{@code
- * BigtableDataSettings bigtableDataSettings = BigtableDataSettings.newBuilder()
+ * BigtableDataSettings.Builder settingsBuilder = BigtableDataSettings.newBuilder()
  *   .setInstanceName(InstanceName.of("my-project", "my-instance-id"))
- *   .setAppProfileId("default")
- *   .build();
+ *   .setAppProfileId("default");
+ *
+ * settingsBuilder.readRowsSettings()
+ *  .setRetryableCodes(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE);
+ *
+ * BigtableDataSettings settings = builder.build();
  * }
* *

This class is considered an internal implementation detail and not meant to be used by @@ -58,14 +68,18 @@ public class EnhancedBigtableStubSettings extends StubSettings readRowsSettings; + private EnhancedBigtableStubSettings(Builder builder) { super(builder); instanceName = builder.instanceName; appProfileId = builder.appProfileId; // Per method settings. + readRowsSettings = builder.readRowsSettings.build(); } + /** Create a new builder. */ public static Builder newBuilder() { return new Builder(); } @@ -80,6 +94,11 @@ public String getAppProfileId() { return appProfileId; } + /** Returns the object with the settings used for calls to ReadRows. */ + public PlaceholderServerStreamingCallSettings readRowsSettings() { + return readRowsSettings; + } + /** Returns a builder containing all the values of this settings class. */ public Builder toBuilder() { return new Builder(this); @@ -90,6 +109,8 @@ public static class Builder extends StubSettings.Builder readRowsSettings; + /** * Initializes a new Builder with sane defaults for all settings. * @@ -114,6 +135,23 @@ private Builder() { .build()); // Per-method settings using baseSettings for defaults. + readRowsSettings = PlaceholderServerStreamingCallSettings.newBuilder(); + /* TODO: copy timeouts, retryCodes & retrySettings from baseSettings.readRows once it exists in GAPIC */ + readRowsSettings + .setRetryableCodes(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE, Code.ABORTED) + .setTimeoutCheckInterval(Duration.ofSeconds(10)) + .setIdleTimeout(Duration.ofMinutes(5)) + .setRetrySettings( + RetrySettings.newBuilder() + .setMaxAttempts(10) + .setTotalTimeout(Duration.ofHours(1)) + .setInitialRetryDelay(Duration.ofMillis(100)) + .setRetryDelayMultiplier(1.3) + .setMaxRetryDelay(Duration.ofMinutes(1)) + .setInitialRpcTimeout(Duration.ofSeconds(20)) + .setRpcTimeoutMultiplier(1) + .setMaxRpcTimeout(Duration.ofSeconds(20)) + .build()); } private Builder(EnhancedBigtableStubSettings settings) { @@ -122,6 +160,7 @@ private Builder(EnhancedBigtableStubSettings settings) { appProfileId = settings.appProfileId; // Per method settings. + readRowsSettings = settings.readRowsSettings.toBuilder(); } // @@ -169,6 +208,11 @@ public String getAppProfileId() { return appProfileId; } + /** Returns the builder for the settings used for calls to readRows. */ + public PlaceholderServerStreamingCallSettings.Builder readRowsSettings() { + return readRowsSettings; + } + @SuppressWarnings("unchecked") public EnhancedBigtableStubSettings build() { Preconditions.checkState(instanceName != null, "InstanceName must be set"); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java new file mode 100644 index 000000000000..867d58cf3ac5 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java @@ -0,0 +1,109 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import java.util.List; + +/** + * Default implementation of a {@link RowAdapter} that uses {@link Row}s to represent logical rows. + */ +public class DefaultRowAdapter implements RowAdapter { + /** {@inheritDoc} */ + @Override + public boolean isScanMarkerRow(Row row) { + return row.cells().isEmpty(); + } + + /** {@inheritDoc} */ + @Override + public RowBuilder createRowBuilder() { + return new DefaultRowBuilder(); + } + + /** {@inheritDoc} */ + @Override + public ByteString getKey(Row row) { + return row.key(); + } + + /** {@inheritDoc} */ + public class DefaultRowBuilder implements RowBuilder { + private ByteString currentKey; + private ImmutableList.Builder cells; + private String family; + private ByteString qualifier; + private List labels; + private long timestamp; + private ByteString value; + + /** {@inheritDoc} */ + @Override + public Row createScanMarkerRow(ByteString key) { + return Row.create(key, ImmutableList.of()); + } + + /** {@inheritDoc} */ + @Override + public void startRow(ByteString key) { + currentKey = key; + cells = ImmutableList.builder(); + } + + /** {@inheritDoc} */ + @Override + public void startCell( + String family, ByteString qualifier, long timestamp, List labels, long size) { + this.family = family; + this.qualifier = qualifier; + this.timestamp = timestamp; + this.labels = labels; + this.value = ByteString.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void cellValue(ByteString value) { + this.value = this.value.concat(value); + } + + /** {@inheritDoc} */ + @Override + public void finishCell() { + cells.add(Cell.create(family, qualifier, timestamp, labels, value)); + } + + /** {@inheritDoc} */ + @Override + public Row finishRow() { + return Row.create(currentKey, cells.build()); + } + + /** {@inheritDoc} */ + @Override + public void reset() { + currentKey = null; + cells = null; + family = null; + qualifier = null; + labels = null; + timestamp = 0; + value = null; + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java new file mode 100644 index 000000000000..f524ea609140 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -0,0 +1,795 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.api.core.InternalApi; +import com.google.bigtable.v2.ColumnRange; +import com.google.bigtable.v2.RowFilter; +import com.google.bigtable.v2.RowFilter.Condition; +import com.google.bigtable.v2.RowFilter.Condition.Builder; +import com.google.bigtable.v2.TimestampRange; +import com.google.bigtable.v2.ValueRange; +import com.google.cloud.bigtable.data.v2.internal.RegexUtil; +import com.google.common.base.Preconditions; +import com.google.protobuf.ByteString; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A Fluent DSL to create a hierarchy of filters for the CheckAndMutateRow RPCs and ReadRows Query. + * + *

Intended usage is to statically import, or in case of conflict assign the static variable + * FILTERS and use its fluent API to build filters. + * + *

Sample code: + * + *

{@code
+ * import static com.google.cloud.bigtable.data.v2.wrappers.Filters.FILTERS;
+ *
+ * void main() {
+ *   // Build the filter expression
+ *   RowFilter filter = FILTERS.chain()
+ *     .filter(FILTERS.qualifier().regex("prefix.*"))
+ *     .filter(FILTERS.limit().cellsPerRow(10));
+ *
+ *   // Use it in a Query
+ *   Query query = Query.create("[TABLE]")
+ *     .filter(filter);
+ * }
+ *
+ * }
+ */ +public final class Filters { + /** Entry point into the DSL. */ + public static final Filters FILTERS = new Filters(); + + private static final SimpleFilter PASS = + new SimpleFilter(RowFilter.newBuilder().setPassAllFilter(true).build()); + private static final SimpleFilter BLOCK = + new SimpleFilter(RowFilter.newBuilder().setBlockAllFilter(true).build()); + private static final SimpleFilter SINK = + new SimpleFilter(RowFilter.newBuilder().setSink(true).build()); + private static final SimpleFilter STRIP_VALUE = + new SimpleFilter(RowFilter.newBuilder().setStripValueTransformer(true).build()); + + private Filters() {} + + /** + * Creates an empty chain filter list. Filters can be added to the chain by invoking {@link + * ChainFilter#filter(Filters.Filter)}. + * + *

The elements of "filters" are chained together to process the input row: + * + *

{@code in row -> filter0 -> intermediate row -> filter1 -> ... -> filterN -> out row}
+   * 
+ * + * The full chain is executed atomically. + */ + public ChainFilter chain() { + return new ChainFilter(); + } + + /** + * Creates an empty interleave filter list. Filters can be added to the interleave by invoking + * {@link InterleaveFilter#filter(Filters.Filter)}. + * + *

The elements of "filters" all process a copy of the input row, and the results are pooled, + * sorted, and combined into a single output row. If multiple cells are produced with the same + * column and timestamp, they will all appear in the output row in an unspecified mutual order. + * The full chain is executed atomically. + */ + public InterleaveFilter interleave() { + return new InterleaveFilter(); + } + + /** + * Creates an empty condition filter. The filter results of the predicate can be configured by + * invoking {@link ConditionFilter#then(Filters.Filter)} and {@link + * ConditionFilter#otherwise(Filters.Filter)}. + * + *

A RowFilter which evaluates one of two possible RowFilters, depending on whether or not a + * predicate RowFilter outputs any cells from the input row. + * + *

IMPORTANT NOTE: The predicate filter does not execute atomically with the {@link + * ConditionFilter#then(Filters.Filter)} and {@link ConditionFilter#otherwise(Filters.Filter)} + * (Filter)} filters, which may lead to inconsistent or unexpected results. Additionally, {@link + * ConditionFilter} may have poor performance, especially when filters are set for the {@link + * ConditionFilter#otherwise(Filters.Filter)}. + */ + public ConditionFilter condition(@Nonnull Filter predicate) { + Preconditions.checkNotNull(predicate); + return new ConditionFilter(predicate); + } + + /** Returns the builder for row key related filters. */ + public KeyFilter key() { + return new KeyFilter(); + } + + /** Returns the builder for column family related filters. */ + public FamilyFilter family() { + return new FamilyFilter(); + } + + /** Returns the builder for column qualifier related filters. */ + public QualifierFilter qualifier() { + return new QualifierFilter(); + } + + /** Returns the builder for timestamp related filters. */ + public TimestampFilter timestamp() { + return new TimestampFilter(); + } + + /** Returns the builder for value related filters. */ + public ValueFilter value() { + return new ValueFilter(); + } + + /** Returns the builder for offset related filters. */ + public OffsetFilter offset() { + return new OffsetFilter(); + } + + /** Returns the builder for limit related filters. */ + public LimitFilter limit() { + return new LimitFilter(); + } + + // Miscellaneous filters without a clear target. + /** Matches all cells, regardless of input. Functionally equivalent to having no filter. */ + public Filter pass() { + return PASS; + } + + /** + * Does not match any cells, regardless of input. Useful for temporarily disabling just part of a + * filter. + */ + public Filter block() { + return BLOCK; + } + + /** + * Outputs all cells directly to the output of the read rather than to any parent filter. For + * advanced usage, see comments in + * https://github.com/googleapis/googleapis/blob/master/google/bigtable/v2/data.proto for more + * details. + */ + public Filter sink() { + return SINK; + } + + /** + * Applies the given label to all cells in the output row. This allows the caller to determine + * which results were produced from which part of the filter. + * + *

Due to a technical limitation, it is not currently possible to apply multiple labels to a + * cell. As a result, a {@link ChainFilter} may have no more than one sub-filter which contains a + * label. It is okay for an {@link InterleaveFilter} to contain multiple labels, as they will be + * applied to separate copies of the input. This may be relaxed in the future. + */ + public Filter label(@Nonnull String label) { + Preconditions.checkNotNull(label); + return new SimpleFilter(RowFilter.newBuilder().setApplyLabelTransformer(label).build()); + } + + // Implementations of target specific filters. + /** DSL for adding filters to a chain. */ + public static final class ChainFilter implements Filter { + private final RowFilter.Chain.Builder builder; + + private ChainFilter() { + this(RowFilter.Chain.newBuilder()); + } + + private ChainFilter(RowFilter.Chain.Builder builder) { + this.builder = builder; + } + + /** Add a filter to chain. */ + public ChainFilter filter(@Nonnull Filter filter) { + Preconditions.checkNotNull(filter); + builder.addFilters(filter.toProto()); + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + if (builder.getFiltersCount() == 1) { + return builder.getFilters(0); + } else { + return RowFilter.newBuilder().setChain(builder.build()).build(); + } + } + + /** Makes a deep copy of the Chain. */ + @Override + public ChainFilter clone() { + return new ChainFilter(builder.clone()); + } + } + + /** DSL for adding filters to the interleave list. */ + public static final class InterleaveFilter implements Filter { + RowFilter.Interleave.Builder builder; + + private InterleaveFilter() { + this(RowFilter.Interleave.newBuilder()); + } + + private InterleaveFilter(RowFilter.Interleave.Builder builder) { + this.builder = builder; + } + + /** Adds a {@link Filter} to the interleave list. */ + public InterleaveFilter filter(@Nonnull Filter filter) { + Preconditions.checkNotNull(filter); + builder.addFilters(filter.toProto()); + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + if (builder.getFiltersCount() == 1) { + return builder.getFilters(0); + } else { + return RowFilter.newBuilder().setInterleave(builder.build()).build(); + } + } + + @Override + protected Object clone() { + return new InterleaveFilter(builder.clone()); + } + } + + /** DSL for configuring a conditional filter. */ + public static final class ConditionFilter implements Filter { + private RowFilter.Condition.Builder builder; + + private ConditionFilter(Builder builder) { + this.builder = builder; + } + + private ConditionFilter(@Nonnull Filter predicate) { + Preconditions.checkNotNull(predicate); + builder = Condition.newBuilder().setPredicateFilter(predicate.toProto()); + } + + /** Sets (replaces) the filter to apply when the predicate is true. */ + public ConditionFilter then(@Nonnull Filter filter) { + Preconditions.checkNotNull(filter); + builder.setTrueFilter(filter.toProto()); + return this; + } + + /** Sets (replaces) the filter to apply when the predicate is false. */ + public ConditionFilter otherwise(@Nonnull Filter filter) { + Preconditions.checkNotNull(filter); + builder.setFalseFilter(filter.toProto()); + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + Preconditions.checkState( + builder.hasTrueFilter() || builder.hasFalseFilter(), + "ConditionFilter must have either a then or otherwise filter."); + return RowFilter.newBuilder().setCondition(builder.build()).build(); + } + + @Override + protected Object clone() { + return new ConditionFilter(builder.clone()); + } + } + + public static final class KeyFilter { + private KeyFilter() {} + + /** + * Matches only cells from rows whose keys satisfy the given RE2 regex. In other words, passes + * through the entire row when the key matches, and otherwise produces an empty row. Note that, + * since row keys can contain arbitrary bytes, the `\C` escape sequence must be used if a true + * wildcard is desired. The `.` character will not match the new line character `\n`, which may + * be present in a binary key. + */ + public Filter regex(@Nonnull String regex) { + Preconditions.checkNotNull(regex); + return regex(wrapString(regex)); + } + + /** + * Matches only cells from rows whose keys satisfy the given RE2 regex. In other words, passes + * through the entire row when the key matches, and otherwise produces an empty row. Note that, + * since row keys can contain arbitrary bytes, the `\C` escape sequence must be used if a true + * wildcard is desired. The `.` character will not match the new line character `\n`, which may + * be present in a binary key. + */ + public Filter regex(@Nonnull ByteString regex) { + Preconditions.checkNotNull(regex); + return new SimpleFilter(RowFilter.newBuilder().setRowKeyRegexFilter(regex).build()); + } + + /** + * Matches only cells from rows whose keys equal the value. In other words, passes through the + * entire row when the key matches, and otherwise produces an empty row. + */ + public Filter exactMatch(@Nonnull ByteString value) { + Preconditions.checkNotNull(value); + + return regex(RegexUtil.literalRegex(value)); + } + + /** + * Matches all cells from a row with `probability`, and matches no cells from the row with + * probability 1-`probability`. + */ + public Filter sample(double probability) { + Preconditions.checkArgument(0 <= probability, "Probability must be positive"); + Preconditions.checkArgument(probability <= 1.0, "Probability must be less than 1.0"); + + return new SimpleFilter(RowFilter.newBuilder().setRowSampleFilter(probability).build()); + } + } + + public static final class FamilyFilter { + private FamilyFilter() {} + + /** + * Matches only cells from columns whose families satisfy the given RE2 regex. For technical reasons, the + * regex must not contain the `:` character, even if it is not being used as a literal. Note + * that, since column families cannot contain the new line character `\n`, it is sufficient to + * use `.` as a full wildcard when matching column family names. + */ + public Filter regex(@Nonnull String regex) { + Preconditions.checkNotNull(regex); + return new SimpleFilter(RowFilter.newBuilder().setFamilyNameRegexFilter(regex).build()); + } + + /** Matches only cells from columns whose families match the value. */ + public Filter exactMatch(@Nonnull String value) { + Preconditions.checkNotNull(value); + return regex(RegexUtil.literalRegex(value)); + } + } + + /** Matches only cells from columns within the given range. */ + public static final class QualifierRangeFilter implements Filter { + private ColumnRange.Builder builder = ColumnRange.newBuilder(); + + private QualifierRangeFilter(ColumnRange.Builder builder) { + this.builder = builder; + } + + private QualifierRangeFilter(@Nonnull String family) { + Preconditions.checkNotNull(family); + builder.setFamilyName(family); + } + + /** Used when giving an inclusive lower bound for the range. */ + public QualifierRangeFilter startClosed(@Nullable String value) { + return startClosed(wrapString(value)); + } + + /** Used when giving an inclusive lower bound for the range. */ + public QualifierRangeFilter startClosed(@Nullable ByteString value) { + if (value == null) { + builder.clearStartQualifier(); + } else { + builder.setStartQualifierClosed(value); + } + return this; + } + + /** Used when giving an exclusive lower bound for the range. */ + public QualifierRangeFilter startOpen(@Nullable String value) { + return startOpen(wrapString(value)); + } + + /** Used when giving an exclusive lower bound for the range. */ + public QualifierRangeFilter startOpen(@Nullable ByteString value) { + if (value == null) { + builder.clearStartQualifier(); + } else { + builder.setStartQualifierOpen(value); + } + return this; + } + + /** Used when giving an inclusive upper bound for the range. */ + public QualifierRangeFilter endClosed(@Nullable String value) { + return endClosed(wrapString(value)); + } + + /** Used when giving an inclusive upper bound for the range. */ + public QualifierRangeFilter endClosed(@Nullable ByteString value) { + if (value == null) { + builder.clearEndQualifier(); + } else { + builder.setEndQualifierClosed(value); + } + return this; + } + + /** Used when giving an exclusive upper bound for the range. */ + public QualifierRangeFilter endOpen(@Nullable String value) { + builder.setEndQualifierOpen(wrapString(value)); + return this; + } + + /** Used when giving an exclusive upper bound for the range. */ + public QualifierRangeFilter endOpen(@Nullable ByteString value) { + if (value == null) { + builder.clearEndQualifier(); + } else { + builder.setEndQualifierOpen(value); + } + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + return RowFilter.newBuilder().setColumnRangeFilter(builder.build()).build(); + } + + @Override + protected Object clone() { + return new QualifierRangeFilter(builder.clone()); + } + } + + public static final class QualifierFilter { + private QualifierFilter() {} + + /** + * Matches only cells from columns whose qualifiers satisfy the given RE2 regex. Note that, since column + * qualifiers can contain arbitrary bytes, the `\C` escape sequence must be used if a true + * wildcard is desired. The `.` character will not match the new line character `\n`, which may + * be present in a binary qualifier. + */ + public Filter regex(@Nonnull String regex) { + return regex(wrapString(regex)); + } + + /** + * Matches only cells from columns whose qualifiers satisfy the given RE2 regex. Note that, since column + * qualifiers can contain arbitrary bytes, the `\C` escape sequence must be used if a true + * wildcard is desired. The `.` character will not match the new line character `\n`, which may + * be present in a binary qualifier. + */ + public Filter regex(@Nonnull ByteString regex) { + Preconditions.checkNotNull(regex); + + return new SimpleFilter(RowFilter.newBuilder().setColumnQualifierRegexFilter(regex).build()); + } + + /** Matches only cells from columns whose qualifiers equal the value. */ + public Filter exactMatch(@Nonnull ByteString value) { + return regex(RegexUtil.literalRegex(value)); + } + + /** + * Construct a {@link QualifierRangeFilter} that can create a {@link ColumnRange} oriented + * {@link Filter}. + * + * @return a new {@link QualifierRangeFilter} + */ + public QualifierRangeFilter rangeWithinFamily(@Nonnull String family) { + Preconditions.checkNotNull(family); + return new QualifierRangeFilter(family); + } + } + + /** + * Matches only cells with microsecond timestamps within the given range. Start is inclusive and + * end is exclusive. + */ + public static final class TimestampRangeFilter implements Filter { + private final TimestampRange.Builder builder; + + private TimestampRangeFilter() { + this(TimestampRange.newBuilder()); + } + + private TimestampRangeFilter(TimestampRange.Builder builder) { + this.builder = builder; + } + + /** + * Inclusive lower bound. If left empty, interpreted as 0. + * + * @param startMicros inclusive timestamp in microseconds. + */ + public TimestampRangeFilter startClosed(long startMicros) { + builder.setStartTimestampMicros(startMicros); + return this; + } + + /** + * Exclusive upper bound. If left empty, interpreted as infinity. + * + * @param endMicros exclusive timestamp in microseconds. + */ + public TimestampRangeFilter endOpen(long endMicros) { + builder.setEndTimestampMicros(endMicros); + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + return RowFilter.newBuilder().setTimestampRangeFilter(builder.build()).build(); + } + + @Override + protected Object clone() { + return new TimestampRangeFilter(builder.clone()); + } + } + + public static final class TimestampFilter { + private TimestampFilter() {} + + /** + * Matches only cells with timestamps within the given range. + * + * @return a {@link TimestampRangeFilter} on which start / end timestamps can be specified. + */ + public TimestampRangeFilter range() { + return new TimestampRangeFilter(); + } + + /** + * Matches only cells with timestamps within the given range. + * + * @param startMicros Inclusive start of the range in microseconds. + * @param endMicros Exclusive end of the range in microseconds. + */ + public Filter range(long startMicros, long endMicros) { + return range().startClosed(startMicros).endOpen(endMicros); + } + } + + /** Matches only cells with values that fall within the given value range. */ + public static final class ValueRangeFilter implements Filter { + private final ValueRange.Builder builder; + + private ValueRangeFilter() { + this(ValueRange.newBuilder()); + } + + private ValueRangeFilter(ValueRange.Builder builder) { + this.builder = builder; + } + + /** Used when giving an inclusive lower bound for the range. */ + public ValueRangeFilter startClosed(@Nullable String value) { + return startClosed(wrapString(value)); + } + + /** Used when giving an inclusive lower bound for the range. */ + public ValueRangeFilter startClosed(@Nullable ByteString value) { + if (value == null) { + builder.clearStartValue(); + } else { + builder.setStartValueClosed(value); + } + return this; + } + + /** Used when giving an exclusive lower bound for the range. */ + public ValueRangeFilter startOpen(@Nullable String value) { + return startOpen(wrapString(value)); + } + + /** Used when giving an exclusive lower bound for the range. */ + public ValueRangeFilter startOpen(@Nullable ByteString value) { + if (value == null) { + builder.clearStartValue(); + } else { + builder.setStartValueOpen(value); + } + return this; + } + + /** Used when giving an inclusive upper bound for the range. */ + public ValueRangeFilter endClosed(@Nullable String value) { + return endClosed(wrapString(value)); + } + + /** Used when giving an inclusive upper bound for the range. */ + public ValueRangeFilter endClosed(@Nullable ByteString value) { + if (value == null) { + builder.clearEndValue(); + } else { + builder.setEndValueClosed(value); + } + return this; + } + + /** Used when giving an exclusive upper bound for the range. */ + public ValueRangeFilter endOpen(@Nullable String value) { + return endOpen(wrapString(value)); + } + + /** Used when giving an exclusive upper bound for the range. */ + public ValueRangeFilter endOpen(@Nullable ByteString value) { + if (value == null) { + builder.clearEndValue(); + } else { + builder.setEndValueOpen(value); + } + return this; + } + + @InternalApi + @Override + public RowFilter toProto() { + return RowFilter.newBuilder().setValueRangeFilter(builder.build()).build(); + } + + @Override + protected Object clone() { + return new ValueRangeFilter(builder.clone()); + } + } + + public static final class ValueFilter { + private ValueFilter() {} + + /** + * Matches only cells with values that satisfy the given RE2 regex. Note that, since cell values + * can contain arbitrary bytes, the `\C` escape sequence must be used if a true wildcard is + * desired. The `.` character will not match the new line character `\n`, which may be present + * in a binary value. + */ + public Filter regex(@Nonnull String regex) { + return regex(wrapString(regex)); + } + + /** Matches only cells with values that match the given value. */ + public Filter exactMatch(@Nonnull ByteString value) { + return regex(RegexUtil.literalRegex(value)); + } + + /** + * Matches only cells with values that satisfy the given RE2 regex. Note that, since cell values + * can contain arbitrary bytes, the `\C` escape sequence must be used if a true wildcard is + * desired. The `.` character will not match the new line character `\n`, which may be present + * in a binary value. + */ + public Filter regex(@Nonnull ByteString regex) { + Preconditions.checkNotNull(regex); + return new SimpleFilter(RowFilter.newBuilder().setValueRegexFilter(regex).build()); + } + + /** + * Construct a {@link ValueRangeFilter} that can create a {@link ValueRange} oriented {@link + * Filter}. + * + * @return a new {@link ValueRangeFilter} + */ + public ValueRangeFilter range() { + return new ValueRangeFilter(); + } + + /** + * Construct a {@link ValueRangeFilter} that can create a {@link ValueRange} oriented {@link + * Filter}. + * + * @return a new {@link ValueRangeFilter} + */ + public ValueRangeFilter range(ByteString start, ByteString end) { + return range().startClosed(start).endOpen(end); + } + + /** + * Construct a {@link ValueRangeFilter} that can create a {@link ValueRange} oriented {@link + * Filter}. + * + * @return a new {@link ValueRangeFilter} + */ + public ValueRangeFilter range(String start, String end) { + return range().startClosed(start).endOpen(end); + } + + /** Replaces each cell's value with the empty string. */ + public Filter strip() { + return STRIP_VALUE; + } + } + + public static final class OffsetFilter { + private OffsetFilter() {} + + /** + * Skips the first N cells of each row, matching all subsequent cells. If duplicate cells are + * present, as is possible when using an {@link InterleaveFilter}, each copy of the cell is + * counted separately. + */ + public Filter cellsPerRow(int count) { + return new SimpleFilter(RowFilter.newBuilder().setCellsPerRowOffsetFilter(count).build()); + } + } + + public static final class LimitFilter { + private LimitFilter() {} + + /** + * Matches only the first N cells of each row. If duplicate cells are present, as is possible + * when using an Interleave, each copy of the cell is counted separately. + */ + public Filter cellsPerRow(int count) { + return new SimpleFilter(RowFilter.newBuilder().setCellsPerRowLimitFilter(count).build()); + } + + /** + * Matches only the most recent `count` cells within each column. For example, if count=2, this + * filter would match column `foo:bar` at timestamps 10 and 9 skip all earlier cells in + * `foo:bar`, and then begin matching again in column `foo:bar2`. If duplicate cells are + * present, as is possible when using an {@link InterleaveFilter}, each copy of the cell is + * counted separately. + */ + public Filter cellsPerColumn(int count) { + return new SimpleFilter(RowFilter.newBuilder().setCellsPerColumnLimitFilter(count).build()); + } + } + + private static final class SimpleFilter implements Filter { + private final RowFilter proto; + + private SimpleFilter(@Nonnull RowFilter proto) { + Preconditions.checkNotNull(proto); + this.proto = proto; + } + + @InternalApi + @Override + public RowFilter toProto() { + return proto; + } + + @Override + protected Object clone() { + return new SimpleFilter(proto); + } + } + + public interface Filter extends Cloneable { + @InternalApi + RowFilter toProto(); + } + + private static ByteString wrapString(@Nullable String value) { + if (value == null) { + return null; + } + return ByteString.copyFromUtf8(value); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java new file mode 100644 index 000000000000..64208b04b6ef --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java @@ -0,0 +1,140 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.api.core.InternalApi; +import com.google.bigtable.v2.ReadRowsRequest; +import com.google.bigtable.v2.RowRange; +import com.google.bigtable.v2.TableName; +import com.google.cloud.bigtable.data.v2.internal.RequestContext; +import com.google.common.base.Preconditions; +import com.google.protobuf.ByteString; + +/** A simple wrapper to construct a query for the ReadRows RPC. */ +public class Query { + private final String tableId; + private final ReadRowsRequest.Builder builder = ReadRowsRequest.newBuilder(); + + /** + * Constructs a new Query object for the specified table id. The table id will be resolved against + * the instance id specified in the {@link + * com.google.cloud.bigtable.data.v2.BigtableDataSettings}. + */ + public static Query create(String tableId) { + return new Query(tableId); + } + + private Query(String tableId) { + this.tableId = tableId; + } + + /** Adds a key to looked up */ + public Query rowKey(String key) { + Preconditions.checkNotNull(key, "Key can't be null."); + return rowKey(ByteString.copyFromUtf8(key)); + } + + /** Adds a key to looked up */ + public Query rowKey(ByteString key) { + Preconditions.checkNotNull(key, "Key can't be null."); + builder.getRowsBuilder().addRowKeys(key); + + return this; + } + + /** + * Adds a range to be looked up. + * + * @param start The beginning of the range (inclusive). Can be null to represent negative + * infinity. + * @param end The end of the range (exclusive). Can be null to represent positive infinity. + */ + public Query range(String start, String end) { + return range(wrapKey(start), wrapKey(end)); + } + + /** + * Adds a range to be looked up. + * + * @param start The beginning of the range (inclusive). Can be null to represent negative + * infinity. + * @param end The end of the range (exclusive). Can be null to represent positive infinity. + */ + public Query range(ByteString start, ByteString end) { + RowRange.Builder rangeBuilder = RowRange.newBuilder(); + if (start != null) { + rangeBuilder.setStartKeyClosed(start); + } + if (end != null) { + rangeBuilder.setEndKeyOpen(end); + } + builder.getRowsBuilder().addRowRanges(rangeBuilder.build()); + return this; + } + + /** Adds a range to be looked up. */ + public Query range(Range range) { + RowRange.Builder rangeBuilder = RowRange.newBuilder(); + if (range.getStart() != null) { + rangeBuilder.setStartKeyClosed(range.getStart()); + } + if (range.getEnd() != null) { + rangeBuilder.setEndKeyOpen(range.getEnd()); + } + builder.getRowsBuilder().addRowRanges(rangeBuilder.build()); + + return this; + } + + /** + * Sets the filter to look apply to each row. Only one filter can be set at a time. To use + * multiple filters, please use {@link Filters#interleave()} or {@link Filters#chain()}. + */ + public Query filter(Filters.Filter filter) { + builder.setFilter(filter.toProto()); + return this; + } + + /** Limits the number of rows that can be returned */ + public Query limit(long limit) { + builder.setRowsLimit(limit); + return this; + } + + /** + * Creates the request protobuf. This method is considered an internal implementation detail and + * not meant to be used by applications. + */ + @InternalApi + public ReadRowsRequest toProto(RequestContext requestContext) { + TableName tableName = + TableName.of( + requestContext.getInstanceName().getProject(), + requestContext.getInstanceName().getInstance(), + tableId); + return builder + .setTableName(tableName.toString()) + .setAppProfileId(requestContext.getAppProfileId()) + .build(); + } + + private static ByteString wrapKey(String key) { + if (key == null) { + return null; + } + return ByteString.copyFromUtf8(key); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java new file mode 100644 index 000000000000..0b1c4652d4a1 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.protobuf.ByteString; +import javax.annotation.Nullable; + +/** + * Simple value object to represent a range of values with an inclusive start and an exclusive end. + * Both endpoints can be null to represent positive/negative infinity. + */ +public class Range { + @Nullable private final T start; + @Nullable private final T end; + + private Range(T start, T end) { + this.start = start; + this.end = end; + } + + /** Creates an unbounded {@link Range}. */ + public static Range unbounded() { + return new Range<>(null, null); + } + + /** Creates a bounded {@link Range}. */ + public static Range of(T start, T end) { + return new Range<>(start, end); + } + + public static Range ofUtf8ByteStrings(String start, String end) { + ByteString startByteString; + if (start == null) { + startByteString = null; + } else { + startByteString = ByteString.copyFromUtf8(start); + } + + ByteString endByteString; + if (end == null) { + endByteString = null; + } else { + endByteString = ByteString.copyFromUtf8(end); + } + + return of(startByteString, endByteString); + } + + /** Creates a new {@link Range} with the specified start and the current end. */ + public Range withStart(T start) { + return new Range<>(start, end); + } + + /** Creates a new {@link Range} with the specified end and the current start. */ + public Range withEnd(T end) { + return new Range<>(start, end); + } + + /** Gets the start of the {@link Range} (inclusive). */ + public T getStart() { + return start; + } + + /** Gets the end of the {@link Range} (exclusive). */ + public T getEnd() { + return end; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java new file mode 100644 index 000000000000..bb472a3aca56 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.protobuf.ByteString; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Default representation of a logical row. */ +@BetaApi +@AutoValue +public abstract class Row implements Comparable { + /** Creates a new instance of the {@link Row}. */ + @InternalApi + public static Row create(ByteString key, List cells) { + return new AutoValue_Row(key, cells); + } + + /** Returns the row key */ + @Nonnull + public abstract ByteString key(); + + /** + * Returns the list of cells. The cells will be clustered by their family and sorted by their + * qualifier. + */ + public abstract List cells(); + + /** Lexicographically compares this row's key to another row's key. */ + @Override + public int compareTo(@Nonnull Row row) { + int sizeA = key().size(); + int sizeB = row.key().size(); + for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) { + int byteA = key().byteAt(i) & 0xff; + int byteB = row.key().byteAt(i) & 0xff; + if (byteA == byteB) { + continue; + } + return byteA < byteB ? -1 : 1; + } + if (sizeA == sizeB) { + return 0; + } + return sizeA < sizeB ? -1 : 1; + } + + /** Default representation of a cell in a {@link Row}. */ + @AutoValue + public abstract static class Cell { + /** Creates a new instance of the {@link Cell}. */ + @InternalApi + public static Cell create( + String family, + ByteString qualifier, + long timestamp, + List labels, + ByteString value) { + return new AutoValue_Row_Cell(family, qualifier, timestamp, value, labels); + } + + /** The cell's family */ + @Nonnull + public abstract String family(); + + /** The cell's qualifier (column name) */ + @Nullable + public abstract ByteString qualifier(); + + /** The timestamp of the cell */ + public abstract long timestamp(); + + /** The value of the cell */ + @Nonnull + public abstract ByteString value(); + + /** + * The labels assigned to the cell + * + * @see Filters#label(String) + */ + @Nonnull + public abstract List labels(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java new file mode 100644 index 000000000000..e7c94dd516e0 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.protobuf.ByteString; +import java.util.List; + +/** + * An extension point that allows end users to plugin a custom implementation of logical rows. This + * useful in cases where the user would like to apply advanced client side filtering of cells. This + * adapter acts like a factory for SAX style a row builder. + */ +public interface RowAdapter { + /** Creates a new instance of a {@link RowBuilder}. */ + RowBuilder createRowBuilder(); + + /** + * Checks if the given row is a special marker row. Please the documentation for {@link + * RowBuilder} for more information + */ + boolean isScanMarkerRow(RowT row); + + ByteString getKey(RowT row); + + /** + * A SAX style row factory. It is responsible for creating two types of rows: standard data rows + * and special marker rows. Marker rows are emitted when skipping lots of rows due to filters. The + * server notifies the client of the last row it skipped to help client resume in case of error. + * + *

State management is handled external to the implementation of this class and guarantees the + * following order: + * + *

    + *
  1. Exactly 1 {@code startRow} for each row. + *
  2. Exactly 1 {@code startCell} for each cell. + *
  3. At least 1 {@code cellValue} for each cell. + *
  4. Exactly 1 {@code finishCell} for each cell. + *
  5. Exactly 1 {@code finishRow} for each row. + *
+ * + * {@code createScanMarkerRow} can be called one or more times between {@code finishRow} and + * {@code startRow}. {@code reset} can be called at any point and can be invoked multiple times in + * a row. + */ + interface RowBuilder { + /** Called to start a new row. This will be called once per row. */ + void startRow(ByteString key); + + /** Called to start a new cell in a row. */ + void startCell( + String family, ByteString qualifier, long timestamp, List labels, long size); + + /** Called multiple times per cell to concatenate the cell value. */ + void cellValue(ByteString value); + + /** Called once per cell to signal the end of the value (unless reset). */ + void finishCell(); + + /** Called once per row to signal that all cells have been processed (unless reset). */ + RowT finishRow(); + + /** Called when the current in progress row should be dropped */ + void reset(); + + /** Creates a special row to mark server progress before any data is received */ + RowT createScanMarkerRow(ByteString key); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java new file mode 100644 index 000000000000..55e3c5180d75 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java @@ -0,0 +1,165 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.gaxx; + +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.api.gax.rpc.StreamingCallSettings; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import java.util.Set; +import org.threeten.bp.Duration; + +/** + * Placeholder for code in the unmerged PR: https://github.com/googleapis/gax-java/pull/463 + * + *

This class will be replaced by the ServerStreamingCallSettings defined there. + */ +public final class PlaceholderServerStreamingCallSettings + extends StreamingCallSettings { + + private final Set retryableCodes; + private final RetrySettings retrySettings; + + private final Duration timeoutCheckInterval; + private final Duration idleTimeout; + + private PlaceholderServerStreamingCallSettings(Builder builder) { + this.retryableCodes = ImmutableSet.copyOf(builder.retryableCodes); + this.retrySettings = builder.retrySettings; + this.timeoutCheckInterval = builder.timeoutCheckInterval; + this.idleTimeout = builder.idleTimeout; + } + + public Set getRetryableCodes() { + return retryableCodes; + } + + public RetrySettings getRetrySettings() { + return retrySettings; + } + + public Duration getTimeoutCheckInterval() { + return timeoutCheckInterval; + } + + public Duration getIdleTimeout() { + return idleTimeout; + } + + public Builder toBuilder() { + return new Builder<>(this); + } + + public static Builder newBuilder() { + return new Builder<>(); + } + + public static class Builder + extends StreamingCallSettings.Builder { + private Set retryableCodes; + private RetrySettings retrySettings; + + private Duration timeoutCheckInterval; + private Duration idleTimeout; + + private Builder() { + this.retryableCodes = ImmutableSet.of(); + this.retrySettings = RetrySettings.newBuilder().build(); + + this.timeoutCheckInterval = Duration.ZERO; + this.idleTimeout = Duration.ZERO; + } + + private Builder(PlaceholderServerStreamingCallSettings settings) { + super(settings); + this.retryableCodes = settings.retryableCodes; + this.retrySettings = settings.retrySettings; + + this.timeoutCheckInterval = settings.timeoutCheckInterval; + this.idleTimeout = settings.idleTimeout; + } + + public Builder setRetryableCodes(StatusCode.Code... codes) { + this.setRetryableCodes(Sets.newHashSet(codes)); + return this; + } + + public Builder setRetryableCodes(Set retryableCodes) { + this.retryableCodes = Sets.newHashSet(retryableCodes); + return this; + } + + public Set getRetryableCodes() { + return retryableCodes; + } + + public Builder setRetrySettings(RetrySettings retrySettings) { + this.retrySettings = retrySettings; + return this; + } + + public RetrySettings getRetrySettings() { + return retrySettings; + } + + public Builder setSimpleTimeoutNoRetries(Duration timeout) { + setRetryableCodes(); + setRetrySettings( + RetrySettings.newBuilder() + .setTotalTimeout(timeout) + .setInitialRetryDelay(Duration.ZERO) + .setRetryDelayMultiplier(1) + .setMaxRetryDelay(Duration.ZERO) + .setInitialRpcTimeout(timeout) + .setRpcTimeoutMultiplier(1) + .setMaxRpcTimeout(timeout) + .setMaxAttempts(1) + .build()); + + // enable watchdog + Duration checkInterval = Ordering.natural().max(timeout.dividedBy(2), Duration.ofSeconds(10)); + setTimeoutCheckInterval(checkInterval); + + return this; + } + + public Duration getTimeoutCheckInterval() { + return timeoutCheckInterval; + } + + public Builder setTimeoutCheckInterval(Duration timeoutCheckInterval) { + this.timeoutCheckInterval = timeoutCheckInterval; + return this; + } + + public Duration getIdleTimeout() { + return idleTimeout; + } + + public Builder setIdleTimeout(Duration idleTimeout) { + this.idleTimeout = idleTimeout; + return this; + } + + @Override + public PlaceholderServerStreamingCallSettings build() { + return new PlaceholderServerStreamingCallSettings<>(this); + } + } +} 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 5c0e80dc2ad6..4ad3f212668e 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 @@ -15,7 +15,12 @@ */ package com.google.cloud.bigtable.data.v2; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.common.truth.Truth; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,11 +31,13 @@ @RunWith(MockitoJUnitRunner.class) public class BigtableDataClientTest { @Mock private EnhancedBigtableStub mockStub; + @Mock private ServerStreamingCallable mockReadRowsCallable; private BigtableDataClient bigtableDataClient; @Before public void setUp() { + Mockito.when(mockStub.readRowsCallable()).thenReturn(mockReadRowsCallable); bigtableDataClient = new BigtableDataClient(mockStub); } @@ -39,4 +46,27 @@ public void proxyCloseTest() throws Exception { bigtableDataClient.close(); Mockito.verify(mockStub).close(); } + + @Test + public void proxyReadRowsCallableTest() { + Truth.assertThat(bigtableDataClient.readRowsCallable()).isSameAs(mockReadRowsCallable); + } + + @Test + public void proxyReadRowsSyncTest() { + Query query = Query.create("fake-table"); + bigtableDataClient.readRows(query); + + Mockito.verify(mockReadRowsCallable).call(query); + } + + @Test + public void proxyReadRowsAsyncTest() { + Query query = Query.create("fake-table"); + @SuppressWarnings("unchecked") + ResponseObserver mockObserver = Mockito.mock(ResponseObserver.class); + bigtableDataClient.readRowsAsync(query, mockObserver); + + Mockito.verify(mockReadRowsCallable).call(query, mockObserver); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java new file mode 100644 index 000000000000..9f7df83c598f --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.internal; + +import com.google.common.truth.Truth; +import com.google.protobuf.ByteString; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RegexUtilTest { + @Test + public void literalRegexPassthroughTest() { + ByteString input = ByteString.copyFromUtf8("hi"); + ByteString actual = RegexUtil.literalRegex(input); + + Truth.assertThat(actual).isEqualTo(input); + } + + @Test + public void literalRegexEscapeTest() { + ByteString input = ByteString.copyFromUtf8("h.*i"); + ByteString actual = RegexUtil.literalRegex(input); + ByteString expected = ByteString.copyFromUtf8("h\\.\\*i"); + + Truth.assertThat(actual).isEqualTo(expected); + } + + @Test + public void literalRegexEscapeBytes() { + ByteString input = ByteString.copyFrom(new byte[] {(byte) 0xe2, (byte) 0x80, (byte) 0xb3}); + ByteString actual = RegexUtil.literalRegex(input); + + Truth.assertThat(actual).isEqualTo(input); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java index 7bdb83d4a172..9a15e32472dc 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java @@ -19,12 +19,19 @@ import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode.Code; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.BigtableDataSettings; +import com.google.cloud.bigtable.data.v2.wrappers.Query; +import com.google.cloud.bigtable.data.v2.wrappers.Row; +import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; +import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import org.threeten.bp.Duration; @RunWith(JUnit4.class) public class EnhancedBigtableStubSettingsTest { @@ -98,4 +105,80 @@ public void multipleChannelsByDefaultTest() { assertThat(provider.toBuilder().getPoolSize()).isGreaterThan(1); } + + @Test + public void readRowsIsNotLostTest() throws IOException { + InstanceName dummyInstanceName = InstanceName.of("my-project", "my-instance"); + + EnhancedBigtableStubSettings.Builder builder = + EnhancedBigtableStubSettings.newBuilder().setInstanceName(dummyInstanceName); + + RetrySettings retrySettings = + RetrySettings.newBuilder() + .setMaxAttempts(10) + .setTotalTimeout(Duration.ofHours(1)) + .setInitialRpcTimeout(Duration.ofSeconds(10)) + .setRpcTimeoutMultiplier(1) + .setMaxRpcTimeout(Duration.ofSeconds(10)) + .setJittered(true) + .build(); + + builder + .readRowsSettings() + .setTimeoutCheckInterval(Duration.ofSeconds(10)) + .setIdleTimeout(Duration.ofMinutes(5)) + .setRetryableCodes(Code.ABORTED, Code.DEADLINE_EXCEEDED) + .setRetrySettings(retrySettings) + .build(); + + assertThat(builder.readRowsSettings().getTimeoutCheckInterval()) + .isEqualTo(Duration.ofSeconds(10)); + assertThat(builder.readRowsSettings().getIdleTimeout()).isEqualTo(Duration.ofMinutes(5)); + assertThat(builder.readRowsSettings().getRetryableCodes()) + .containsAllOf(Code.ABORTED, Code.DEADLINE_EXCEEDED); + assertThat(builder.readRowsSettings().getRetrySettings()).isEqualTo(retrySettings); + + assertThat(builder.build().readRowsSettings().getTimeoutCheckInterval()) + .isEqualTo(Duration.ofSeconds(10)); + assertThat(builder.build().readRowsSettings().getIdleTimeout()) + .isEqualTo(Duration.ofMinutes(5)); + assertThat(builder.build().readRowsSettings().getRetryableCodes()) + .containsAllOf(Code.ABORTED, Code.DEADLINE_EXCEEDED); + assertThat(builder.build().readRowsSettings().getRetrySettings()) + .isEqualTo(retrySettings); + + assertThat(builder.build().toBuilder().readRowsSettings().getTimeoutCheckInterval()) + .isEqualTo(Duration.ofSeconds(10)); + assertThat(builder.build().toBuilder().readRowsSettings().getIdleTimeout()) + .isEqualTo(Duration.ofMinutes(5)); + assertThat(builder.build().toBuilder().readRowsSettings().getRetryableCodes()) + .containsAllOf(Code.ABORTED, Code.DEADLINE_EXCEEDED); + assertThat(builder.build().toBuilder().readRowsSettings().getRetrySettings()) + .isEqualTo(retrySettings); + } + + @Test + public void readRowsHasSaneDefaultsTest() { + PlaceholderServerStreamingCallSettings.Builder builder = + EnhancedBigtableStubSettings.newBuilder().readRowsSettings(); + + assertThat(builder.getTimeoutCheckInterval()).isGreaterThan(Duration.ZERO); + assertThat(builder.getIdleTimeout()).isGreaterThan(Duration.ZERO); + + assertThat(builder.getRetryableCodes()) + .containsAllOf(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE, Code.ABORTED); + + assertThat(builder.getRetrySettings().getMaxAttempts()).isGreaterThan(1); + assertThat(builder.getRetrySettings().getTotalTimeout()).isGreaterThan(Duration.ZERO); + + assertThat(builder.getRetrySettings().getInitialRetryDelay()) + .isGreaterThan(Duration.ZERO); + assertThat(builder.getRetrySettings().getRetryDelayMultiplier()).isAtLeast(1.0); + assertThat(builder.getRetrySettings().getMaxRetryDelay()).isGreaterThan(Duration.ZERO); + + assertThat(builder.getRetrySettings().getInitialRpcTimeout()) + .isGreaterThan(Duration.ZERO); + assertThat(builder.getRetrySettings().getRpcTimeoutMultiplier()).isAtLeast(1.0); + assertThat(builder.getRetrySettings().getMaxRpcTimeout()).isGreaterThan(Duration.ZERO); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java new file mode 100644 index 000000000000..36c871838518 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; +import com.google.cloud.bigtable.data.v2.wrappers.RowAdapter.RowBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.protobuf.ByteString; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DefaultRowAdapterTest { + + private DefaultRowAdapter adapter = new DefaultRowAdapter(); + private RowBuilder rowBuilder; + + @Before + public void setUp() { + rowBuilder = adapter.createRowBuilder(); + } + + @Test + public void singleCellRowTest() { + ByteString value = ByteString.copyFromUtf8("my-value"); + rowBuilder.startRow(ByteString.copyFromUtf8("my-key")); + rowBuilder.startCell( + "my-family", + ByteString.copyFromUtf8("my-qualifier"), + 100, + ImmutableList.of("my-label"), + value.size()); + rowBuilder.cellValue(value); + rowBuilder.finishCell(); + + assertThat(rowBuilder.finishRow()) + .isEqualTo( + Row.create( + ByteString.copyFromUtf8("my-key"), + ImmutableList.of( + Cell.create( + "my-family", + ByteString.copyFromUtf8("my-qualifier"), + 100, + ImmutableList.of("my-label"), + value)))); + } + + @Test + public void multiCellTest() { + List expectedCells = Lists.newArrayList(); + + rowBuilder.startRow(ByteString.copyFromUtf8("my-key")); + + for (int i = 0; i < 10; i++) { + ByteString value = ByteString.copyFromUtf8("value-" + i); + ByteString qualifier = ByteString.copyFromUtf8("qualifier-" + i); + rowBuilder.startCell("family", qualifier, 1000, ImmutableList.of("my-label"), value.size()); + rowBuilder.cellValue(value); + rowBuilder.finishCell(); + + expectedCells.add( + Cell.create("family", qualifier, 1000, ImmutableList.of("my-label"), value)); + } + + assertThat(rowBuilder.finishRow()) + .isEqualTo(Row.create(ByteString.copyFromUtf8("my-key"), expectedCells)); + } + + @Test + public void splitCellTest() { + ByteString part1 = ByteString.copyFromUtf8("part1"); + ByteString part2 = ByteString.copyFromUtf8("part2"); + + rowBuilder.startRow(ByteString.copyFromUtf8("my-key")); + rowBuilder.startCell( + "family", + ByteString.copyFromUtf8("qualifier"), + 1000, + ImmutableList.of("my-label"), + part1.size() + part2.size()); + rowBuilder.cellValue(part1); + rowBuilder.cellValue(part2); + rowBuilder.finishCell(); + + assertThat(rowBuilder.finishRow()) + .isEqualTo( + Row.create( + ByteString.copyFromUtf8("my-key"), + ImmutableList.of( + Cell.create( + "family", + ByteString.copyFromUtf8("qualifier"), + 1000, + ImmutableList.of("my-label"), + ByteString.copyFromUtf8("part1part2"))))); + } + + @Test + public void markerRowTest() { + Row markerRow = rowBuilder.createScanMarkerRow(ByteString.copyFromUtf8("key")); + assertThat(adapter.isScanMarkerRow(markerRow)).isTrue(); + + ByteString value = ByteString.copyFromUtf8("value"); + rowBuilder.startRow(ByteString.copyFromUtf8("key")); + rowBuilder.startCell( + "family", ByteString.EMPTY, 1000, ImmutableList.of(), value.size()); + rowBuilder.cellValue(value); + rowBuilder.finishCell(); + + assertThat(adapter.isScanMarkerRow(rowBuilder.finishRow())).isFalse(); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java new file mode 100644 index 000000000000..b59e6fac6eb5 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java @@ -0,0 +1,399 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import static com.google.cloud.bigtable.data.v2.wrappers.Filters.FILTERS; +import static com.google.common.truth.Truth.assertThat; + +import com.google.bigtable.v2.ColumnRange; +import com.google.bigtable.v2.RowFilter; +import com.google.bigtable.v2.RowFilter.Chain; +import com.google.bigtable.v2.RowFilter.Condition; +import com.google.bigtable.v2.RowFilter.Interleave; +import com.google.bigtable.v2.ValueRange; +import com.google.protobuf.ByteString; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FiltersTest { + @Test + public void chainTest() { + RowFilter actualProto = + FILTERS + .chain() + .filter(FILTERS.key().regex(".*")) + .filter(FILTERS.key().sample(0.5)) + .filter(FILTERS.chain().filter(FILTERS.family().regex("hi$")).filter(FILTERS.pass())) + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setChain( + Chain.newBuilder() + .addFilters( + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*"))) + .addFilters(RowFilter.newBuilder().setRowSampleFilter(0.5)) + .addFilters( + RowFilter.newBuilder() + .setChain( + Chain.newBuilder() + .addFilters( + RowFilter.newBuilder().setFamilyNameRegexFilter("hi$")) + .addFilters(RowFilter.newBuilder().setPassAllFilter(true))))) + .build(); + + assertThat(actualProto).isEqualTo(expectedFilter); + } + + @Test + public void interleaveTest() { + RowFilter actualProto = + FILTERS + .interleave() + .filter(FILTERS.key().regex(".*")) + .filter(FILTERS.key().sample(0.5)) + .filter( + FILTERS.interleave().filter(FILTERS.family().regex("hi$")).filter(FILTERS.pass())) + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setInterleave( + Interleave.newBuilder() + .addFilters( + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*"))) + .addFilters(RowFilter.newBuilder().setRowSampleFilter(0.5)) + .addFilters( + RowFilter.newBuilder() + .setInterleave( + Interleave.newBuilder() + .addFilters( + RowFilter.newBuilder().setFamilyNameRegexFilter("hi$")) + .addFilters( + RowFilter.newBuilder().setPassAllFilter(true).build())))) + .build(); + + assertThat(actualProto).isEqualTo(expectedFilter); + } + + @Test + public void conditionTest() { + RowFilter actualFilter = + FILTERS + .condition(FILTERS.key().regex(".*")) + .then(FILTERS.label("true")) + .otherwise(FILTERS.label("false")) + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setCondition( + Condition.newBuilder() + .setPredicateFilter( + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*"))) + .setTrueFilter(RowFilter.newBuilder().setApplyLabelTransformer("true")) + .setFalseFilter(RowFilter.newBuilder().setApplyLabelTransformer("false"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void passTest() { + RowFilter actualFilter = FILTERS.pass().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setPassAllFilter(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void blockTest() { + RowFilter actualFilter = FILTERS.block().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setBlockAllFilter(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void sinkTest() { + RowFilter actualFilter = FILTERS.sink().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setSink(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void labelTest() { + RowFilter actualFilter = FILTERS.label("my-label").toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setApplyLabelTransformer("my-label").build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void keyRegexTest() { + RowFilter actualFilter = FILTERS.key().regex(".*").toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*")).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void keyExactMatchTest() { + RowFilter actualFilter = FILTERS.key().exactMatch(ByteString.copyFromUtf8(".*")).toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8("\\.\\*")).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void keySampleTest() { + RowFilter actualFilter = FILTERS.key().sample(0.3).toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setRowSampleFilter(0.3).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void familyRegexTest() { + RowFilter actualFilter = FILTERS.family().regex("^hi").toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setFamilyNameRegexFilter("^hi").build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void familyExactMatchTest() { + RowFilter actualFilter = FILTERS.family().exactMatch("^hi").toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setFamilyNameRegexFilter("\\^hi").build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierRegexTest() { + RowFilter actualFilter = FILTERS.qualifier().regex("^hi").toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnQualifierRegexFilter(ByteString.copyFromUtf8("^hi")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierExactMatchTest() { + RowFilter actualFilter = + FILTERS.qualifier().exactMatch(ByteString.copyFromUtf8("^hi")).toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnQualifierRegexFilter(ByteString.copyFromUtf8("\\^hi")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierRangeSimple() { + RowFilter actualFilter = + FILTERS + .qualifier() + .rangeWithinFamily("family") + .startClosed("begin") + .endOpen("end") + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnRangeFilter( + ColumnRange.newBuilder() + .setFamilyName("family") + .setStartQualifierClosed(ByteString.copyFromUtf8("begin")) + .setEndQualifierOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierRangeByte() { + RowFilter actualFilter = + FILTERS + .qualifier() + .rangeWithinFamily("family") + .startClosed("begin") + .endOpen("end") + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnRangeFilter( + ColumnRange.newBuilder() + .setFamilyName("family") + .setStartQualifierClosed(ByteString.copyFromUtf8("begin")) + .setEndQualifierOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierRangeRange() { + RowFilter actualFilter = + FILTERS + .qualifier() + .rangeWithinFamily("family") + .startClosed("begin") + .endOpen("end") + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnRangeFilter( + ColumnRange.newBuilder() + .setFamilyName("family") + .setStartQualifierClosed(ByteString.copyFromUtf8("begin")) + .setEndQualifierOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueRegex() { + RowFilter actualFilter = FILTERS.value().regex("some[0-9]regex").toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRegexFilter(ByteString.copyFromUtf8("some[0-9]regex")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueExactMatch() { + RowFilter actualFilter = + FILTERS.value().exactMatch(ByteString.copyFromUtf8("some[0-9]regex")).toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRegexFilter(ByteString.copyFromUtf8("some\\[0\\-9\\]regex")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueRangeSimple() { + RowFilter actualFilter = FILTERS.value().range("begin", "end").toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRangeFilter( + ValueRange.newBuilder() + .setStartValueClosed(ByteString.copyFromUtf8("begin")) + .setEndValueOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueRangeByte() { + RowFilter actualFilter = + FILTERS + .value() + .range(ByteString.copyFromUtf8("begin"), ByteString.copyFromUtf8("end")) + .toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRangeFilter( + ValueRange.newBuilder() + .setStartValueClosed(ByteString.copyFromUtf8("begin")) + .setEndValueOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueRangeRange() { + RowFilter actualFilter = FILTERS.value().range().startClosed("begin").endOpen("end").toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRangeFilter( + ValueRange.newBuilder() + .setStartValueClosed(ByteString.copyFromUtf8("begin")) + .setEndValueOpen(ByteString.copyFromUtf8("end"))) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueStripTest() { + RowFilter actualFilter = FILTERS.value().strip().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setStripValueTransformer(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void offsetCellsPerRowTest() { + RowFilter actualFilter = FILTERS.offset().cellsPerRow(10).toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setCellsPerRowOffsetFilter(10).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void limitCellsPerRowTest() { + RowFilter actualFilter = FILTERS.limit().cellsPerRow(10).toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setCellsPerRowLimitFilter(10).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void limitCellsPerColumnTest() { + RowFilter actualFilter = FILTERS.limit().cellsPerColumn(10).toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setCellsPerColumnLimitFilter(10).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java new file mode 100644 index 000000000000..9e284fcc100f --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import static com.google.cloud.bigtable.data.v2.wrappers.Filters.FILTERS; +import static com.google.common.truth.Truth.assertThat; + +import com.google.bigtable.admin.v2.InstanceName; +import com.google.bigtable.v2.ReadRowsRequest; +import com.google.bigtable.v2.ReadRowsRequest.Builder; +import com.google.bigtable.v2.RowFilter; +import com.google.bigtable.v2.RowRange; +import com.google.bigtable.v2.TableName; +import com.google.cloud.bigtable.data.v2.internal.RequestContext; +import com.google.protobuf.ByteString; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class QueryTest { + private static final InstanceName INSTANCE_NAME = + InstanceName.of("fake-project", "fake-instance"); + private static final TableName TABLE_NAME = + TableName.of("fake-project", "fake-instance", "fake-table"); + private static final String APP_PROFILE_ID = "fake-profile-id"; + private RequestContext requestContext; + + @Before + public void setUp() { + requestContext = RequestContext.create(INSTANCE_NAME, APP_PROFILE_ID); + } + + @Test + public void requestContextTest() { + Query query = Query.create(TABLE_NAME.getTable()); + + ReadRowsRequest proto = query.toProto(requestContext); + assertThat(proto).isEqualTo(expectedProtoBuilder().build()); + } + + @Test + public void rowKeysTest() { + Query query = + Query.create(TABLE_NAME.getTable()) + .rowKey("simple-string") + .rowKey(ByteString.copyFromUtf8("byte-string")); + + ReadRowsRequest.Builder expectedProto = expectedProtoBuilder(); + expectedProto + .getRowsBuilder() + .addRowKeys(ByteString.copyFromUtf8("simple-string")) + .addRowKeys(ByteString.copyFromUtf8("byte-string")); + + ReadRowsRequest actualProto = query.toProto(requestContext); + assertThat(actualProto).isEqualTo(expectedProto.build()); + } + + @Test + public void rowRangeTest() { + Query query = + Query.create(TABLE_NAME.getTable()) + .range("simple-begin", "simple-end") + .range(ByteString.copyFromUtf8("byte-begin"), ByteString.copyFromUtf8("byte-end")) + .range(Range.ofUtf8ByteStrings("range-begin", "range-end")); + + Builder expectedProto = expectedProtoBuilder(); + expectedProto + .getRowsBuilder() + .addRowRanges( + RowRange.newBuilder() + .setStartKeyClosed(ByteString.copyFromUtf8("simple-begin")) + .setEndKeyOpen(ByteString.copyFromUtf8("simple-end"))) + .addRowRanges( + RowRange.newBuilder() + .setStartKeyClosed(ByteString.copyFromUtf8("byte-begin")) + .setEndKeyOpen(ByteString.copyFromUtf8("byte-end"))) + .addRowRanges( + RowRange.newBuilder() + .setStartKeyClosed(ByteString.copyFromUtf8("range-begin")) + .setEndKeyOpen(ByteString.copyFromUtf8("range-end"))); + + ReadRowsRequest actualProto = query.toProto(requestContext); + assertThat(actualProto).isEqualTo(expectedProto.build()); + } + + @Test + public void filterTest() { + Query query = Query.create(TABLE_NAME.getTable()).filter(FILTERS.key().regex(".*")); + + Builder expectedProto = + expectedProtoBuilder() + .setFilter(RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*"))); + + ReadRowsRequest actualProto = query.toProto(requestContext); + assertThat(actualProto).isEqualTo(expectedProto.build()); + } + + @Test + public void limitTest() { + Query query = Query.create(TABLE_NAME.getTable()).limit(10); + + Builder expectedProto = expectedProtoBuilder().setRowsLimit(10); + + ReadRowsRequest actualProto = query.toProto(requestContext); + assertThat(actualProto).isEqualTo(expectedProto.build()); + } + + private static ReadRowsRequest.Builder expectedProtoBuilder() { + return ReadRowsRequest.newBuilder() + .setTableName(TABLE_NAME.toString()) + .setAppProfileId(APP_PROFILE_ID); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java new file mode 100644 index 000000000000..fdb0507ba29c --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.protobuf.ByteString; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RangeTest { + @Test + public void utf8StringsTest() { + Range range = Range.ofUtf8ByteStrings("begin", "end"); + + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("begin")); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("end")); + } + + @Test + public void changeStartTest() { + Range range = + Range.ofUtf8ByteStrings("begin", "end").withStart(ByteString.copyFromUtf8("begin2")); + + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("begin2")); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("end")); + } + + @Test + public void changeEndTest() { + Range range = Range.of(10, 20).withEnd(30); + + assertThat(range.getStart()).isEqualTo(10); + assertThat(range.getEnd()).isEqualTo(30); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java new file mode 100644 index 000000000000..84cda0dbf316 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RowTest { + @Test + public void compareTest() { + Row row1 = + Row.create( + ByteString.copyFromUtf8("key1"), + ImmutableList.of( + Cell.create( + "family", + ByteString.EMPTY, + 1000, + ImmutableList.of(), + ByteString.copyFromUtf8("value")))); + Row row2 = + Row.create( + ByteString.copyFromUtf8("key2"), + ImmutableList.of( + Cell.create( + "family", + ByteString.EMPTY, + 1000, + ImmutableList.of(), + ByteString.copyFromUtf8("value")))); + Row row2b = + Row.create( + ByteString.copyFromUtf8("key2"), + ImmutableList.of( + Cell.create( + "family2", + ByteString.EMPTY, + 1000, + ImmutableList.of(), + ByteString.copyFromUtf8("value")))); + + assertThat(row1).isEqualTo(row1); + assertThat(row1).isLessThan(row2); + assertThat(row2).isGreaterThan(row1); + + // Comparator only cares about row keys + assertThat(row2).isEquivalentAccordingToCompareTo(row2b); + } +} From 8b838cd9eaa0ff4ba9c880eaf66903e5d33dd66e Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 5 Feb 2018 13:35:40 -0500 Subject: [PATCH 02/18] codacy fixes --- .../v2/stub/EnhancedBigtableStubSettings.java | 2 +- .../bigtable/data/v2/wrappers/Filters.java | 104 ++++++++++-------- .../cloud/bigtable/data/v2/wrappers/Row.java | 7 +- .../EnhancedBigtableStubSettingsTest.java | 9 +- .../v2/wrappers/DefaultRowAdapterTest.java | 2 +- 5 files changed, 68 insertions(+), 56 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index e69d8766d82d..28475c15792c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -109,7 +109,7 @@ public static class Builder extends StubSettings.Builder readRowsSettings; + private final PlaceholderServerStreamingCallSettings.Builder readRowsSettings; /** * Initializes a new Builder with sane defaults for all settings. diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index f524ea609140..5bfdbc790f55 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -190,14 +190,10 @@ public Filter label(@Nonnull String label) { // Implementations of target specific filters. /** DSL for adding filters to a chain. */ public static final class ChainFilter implements Filter { - private final RowFilter.Chain.Builder builder; + private RowFilter.Chain.Builder builder; private ChainFilter() { - this(RowFilter.Chain.newBuilder()); - } - - private ChainFilter(RowFilter.Chain.Builder builder) { - this.builder = builder; + this.builder = RowFilter.Chain.newBuilder(); } /** Add a filter to chain. */ @@ -220,20 +216,22 @@ public RowFilter toProto() { /** Makes a deep copy of the Chain. */ @Override public ChainFilter clone() { - return new ChainFilter(builder.clone()); + try { + ChainFilter clone = (ChainFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } /** DSL for adding filters to the interleave list. */ public static final class InterleaveFilter implements Filter { - RowFilter.Interleave.Builder builder; + private RowFilter.Interleave.Builder builder; private InterleaveFilter() { - this(RowFilter.Interleave.newBuilder()); - } - - private InterleaveFilter(RowFilter.Interleave.Builder builder) { - this.builder = builder; + builder = RowFilter.Interleave.newBuilder(); } /** Adds a {@link Filter} to the interleave list. */ @@ -254,8 +252,14 @@ public RowFilter toProto() { } @Override - protected Object clone() { - return new InterleaveFilter(builder.clone()); + protected InterleaveFilter clone() { + try { + InterleaveFilter clone = (InterleaveFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -263,10 +267,6 @@ protected Object clone() { public static final class ConditionFilter implements Filter { private RowFilter.Condition.Builder builder; - private ConditionFilter(Builder builder) { - this.builder = builder; - } - private ConditionFilter(@Nonnull Filter predicate) { Preconditions.checkNotNull(predicate); builder = Condition.newBuilder().setPredicateFilter(predicate.toProto()); @@ -296,8 +296,14 @@ public RowFilter toProto() { } @Override - protected Object clone() { - return new ConditionFilter(builder.clone()); + protected ConditionFilter clone() { + try { + ConditionFilter clone = (ConditionFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -376,15 +382,11 @@ public Filter exactMatch(@Nonnull String value) { /** Matches only cells from columns within the given range. */ public static final class QualifierRangeFilter implements Filter { - private ColumnRange.Builder builder = ColumnRange.newBuilder(); - - private QualifierRangeFilter(ColumnRange.Builder builder) { - this.builder = builder; - } + private ColumnRange.Builder builder; private QualifierRangeFilter(@Nonnull String family) { Preconditions.checkNotNull(family); - builder.setFamilyName(family); + builder = ColumnRange.newBuilder().setFamilyName(family); } /** Used when giving an inclusive lower bound for the range. */ @@ -455,8 +457,14 @@ public RowFilter toProto() { } @Override - protected Object clone() { - return new QualifierRangeFilter(builder.clone()); + protected QualifierRangeFilter clone() { + try { + QualifierRangeFilter clone = (QualifierRangeFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -509,14 +517,10 @@ public QualifierRangeFilter rangeWithinFamily(@Nonnull String family) { * end is exclusive. */ public static final class TimestampRangeFilter implements Filter { - private final TimestampRange.Builder builder; + private TimestampRange.Builder builder; private TimestampRangeFilter() { - this(TimestampRange.newBuilder()); - } - - private TimestampRangeFilter(TimestampRange.Builder builder) { - this.builder = builder; + builder = TimestampRange.newBuilder(); } /** @@ -546,8 +550,14 @@ public RowFilter toProto() { } @Override - protected Object clone() { - return new TimestampRangeFilter(builder.clone()); + protected TimestampRangeFilter clone() { + try { + TimestampRangeFilter clone = (TimestampRangeFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -576,14 +586,10 @@ public Filter range(long startMicros, long endMicros) { /** Matches only cells with values that fall within the given value range. */ public static final class ValueRangeFilter implements Filter { - private final ValueRange.Builder builder; + private ValueRange.Builder builder; private ValueRangeFilter() { - this(ValueRange.newBuilder()); - } - - private ValueRangeFilter(ValueRange.Builder builder) { - this.builder = builder; + builder = ValueRange.newBuilder(); } /** Used when giving an inclusive lower bound for the range. */ @@ -653,8 +659,14 @@ public RowFilter toProto() { } @Override - protected Object clone() { - return new ValueRangeFilter(builder.clone()); + public ValueRangeFilter clone() { + try { + ValueRangeFilter clone = (ValueRangeFilter) super.clone(); + clone.builder = builder.clone(); + return clone; + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -776,7 +788,7 @@ public RowFilter toProto() { } @Override - protected Object clone() { + public Object clone() { return new SimpleFilter(proto); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java index bb472a3aca56..e3e8c42b04f3 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java @@ -48,13 +48,16 @@ public static Row create(ByteString key, List cells) { public int compareTo(@Nonnull Row row) { int sizeA = key().size(); int sizeB = row.key().size(); - for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) { + int size = Math.min(sizeA, sizeB); + + for (int i = 0; i < size; i++) { int byteA = key().byteAt(i) & 0xff; int byteB = row.key().byteAt(i) & 0xff; if (byteA == byteB) { continue; + } else { + return byteA < byteB ? -1 : 1; } - return byteA < byteB ? -1 : 1; } if (sizeA == sizeB) { return 0; diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java index 9a15e32472dc..5b8ebfd4041a 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java @@ -144,8 +144,7 @@ public void readRowsIsNotLostTest() throws IOException { .isEqualTo(Duration.ofMinutes(5)); assertThat(builder.build().readRowsSettings().getRetryableCodes()) .containsAllOf(Code.ABORTED, Code.DEADLINE_EXCEEDED); - assertThat(builder.build().readRowsSettings().getRetrySettings()) - .isEqualTo(retrySettings); + assertThat(builder.build().readRowsSettings().getRetrySettings()).isEqualTo(retrySettings); assertThat(builder.build().toBuilder().readRowsSettings().getTimeoutCheckInterval()) .isEqualTo(Duration.ofSeconds(10)); @@ -171,13 +170,11 @@ public void readRowsHasSaneDefaultsTest() { assertThat(builder.getRetrySettings().getMaxAttempts()).isGreaterThan(1); assertThat(builder.getRetrySettings().getTotalTimeout()).isGreaterThan(Duration.ZERO); - assertThat(builder.getRetrySettings().getInitialRetryDelay()) - .isGreaterThan(Duration.ZERO); + assertThat(builder.getRetrySettings().getInitialRetryDelay()).isGreaterThan(Duration.ZERO); assertThat(builder.getRetrySettings().getRetryDelayMultiplier()).isAtLeast(1.0); assertThat(builder.getRetrySettings().getMaxRetryDelay()).isGreaterThan(Duration.ZERO); - assertThat(builder.getRetrySettings().getInitialRpcTimeout()) - .isGreaterThan(Duration.ZERO); + assertThat(builder.getRetrySettings().getInitialRpcTimeout()).isGreaterThan(Duration.ZERO); assertThat(builder.getRetrySettings().getRpcTimeoutMultiplier()).isAtLeast(1.0); assertThat(builder.getRetrySettings().getMaxRpcTimeout()).isGreaterThan(Duration.ZERO); } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java index 36c871838518..212e7201a5f2 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java @@ -31,7 +31,7 @@ @RunWith(JUnit4.class) public class DefaultRowAdapterTest { - private DefaultRowAdapter adapter = new DefaultRowAdapter(); + private final DefaultRowAdapter adapter = new DefaultRowAdapter(); private RowBuilder rowBuilder; @Before From cd41c0ff9d1730ec522908acfc4a8de1a7a53b4c Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 5 Feb 2018 20:37:31 -0500 Subject: [PATCH 03/18] Refactor Range support to allow for a consistent api --- .../bigtable/data/v2/wrappers/Filters.java | 375 ++++++++---------- .../bigtable/data/v2/wrappers/Query.java | 28 +- .../bigtable/data/v2/wrappers/Range.java | 211 ++++++++-- .../cloud/bigtable/data/v2/wrappers/Row.java | 2 +- .../data/v2/wrappers/FiltersTest.java | 159 +++++--- .../bigtable/data/v2/wrappers/QueryTest.java | 3 +- .../bigtable/data/v2/wrappers/RangeTest.java | 175 +++++++- 7 files changed, 622 insertions(+), 331 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index 5bfdbc790f55..6989dcda881d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -19,10 +19,10 @@ import com.google.bigtable.v2.ColumnRange; import com.google.bigtable.v2.RowFilter; import com.google.bigtable.v2.RowFilter.Condition; -import com.google.bigtable.v2.RowFilter.Condition.Builder; -import com.google.bigtable.v2.TimestampRange; import com.google.bigtable.v2.ValueRange; import com.google.cloud.bigtable.data.v2.internal.RegexUtil; +import com.google.cloud.bigtable.data.v2.wrappers.Range.AbstractByteStringRange; +import com.google.cloud.bigtable.data.v2.wrappers.Range.AbstractTimestampRange; import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; import javax.annotation.Nonnull; @@ -54,6 +54,7 @@ */ public final class Filters { /** Entry point into the DSL. */ + @SuppressWarnings("WeakerAccess") public static final Filters FILTERS = new Filters(); private static final SimpleFilter PASS = @@ -380,94 +381,6 @@ public Filter exactMatch(@Nonnull String value) { } } - /** Matches only cells from columns within the given range. */ - public static final class QualifierRangeFilter implements Filter { - private ColumnRange.Builder builder; - - private QualifierRangeFilter(@Nonnull String family) { - Preconditions.checkNotNull(family); - builder = ColumnRange.newBuilder().setFamilyName(family); - } - - /** Used when giving an inclusive lower bound for the range. */ - public QualifierRangeFilter startClosed(@Nullable String value) { - return startClosed(wrapString(value)); - } - - /** Used when giving an inclusive lower bound for the range. */ - public QualifierRangeFilter startClosed(@Nullable ByteString value) { - if (value == null) { - builder.clearStartQualifier(); - } else { - builder.setStartQualifierClosed(value); - } - return this; - } - - /** Used when giving an exclusive lower bound for the range. */ - public QualifierRangeFilter startOpen(@Nullable String value) { - return startOpen(wrapString(value)); - } - - /** Used when giving an exclusive lower bound for the range. */ - public QualifierRangeFilter startOpen(@Nullable ByteString value) { - if (value == null) { - builder.clearStartQualifier(); - } else { - builder.setStartQualifierOpen(value); - } - return this; - } - - /** Used when giving an inclusive upper bound for the range. */ - public QualifierRangeFilter endClosed(@Nullable String value) { - return endClosed(wrapString(value)); - } - - /** Used when giving an inclusive upper bound for the range. */ - public QualifierRangeFilter endClosed(@Nullable ByteString value) { - if (value == null) { - builder.clearEndQualifier(); - } else { - builder.setEndQualifierClosed(value); - } - return this; - } - - /** Used when giving an exclusive upper bound for the range. */ - public QualifierRangeFilter endOpen(@Nullable String value) { - builder.setEndQualifierOpen(wrapString(value)); - return this; - } - - /** Used when giving an exclusive upper bound for the range. */ - public QualifierRangeFilter endOpen(@Nullable ByteString value) { - if (value == null) { - builder.clearEndQualifier(); - } else { - builder.setEndQualifierOpen(value); - } - return this; - } - - @InternalApi - @Override - public RowFilter toProto() { - return RowFilter.newBuilder().setColumnRangeFilter(builder.build()).build(); - } - - @Override - protected QualifierRangeFilter clone() { - try { - QualifierRangeFilter clone = (QualifierRangeFilter) super.clone(); - clone.builder = builder.clone(); - return clone; - } catch (CloneNotSupportedException | ClassCastException e) { - throw new RuntimeException("should never happen"); - } - } - } - public static final class QualifierFilter { private QualifierFilter() {} @@ -512,49 +425,65 @@ public QualifierRangeFilter rangeWithinFamily(@Nonnull String family) { } } - /** - * Matches only cells with microsecond timestamps within the given range. Start is inclusive and - * end is exclusive. - */ - public static final class TimestampRangeFilter implements Filter { - private TimestampRange.Builder builder; + /** Matches only cells from columns within the given range. */ + public static final class QualifierRangeFilter + extends AbstractByteStringRange implements Filter { + private @Nonnull String family; - private TimestampRangeFilter() { - builder = TimestampRange.newBuilder(); + private QualifierRangeFilter(String family) { + super(); + this.family = family; } - /** - * Inclusive lower bound. If left empty, interpreted as 0. - * - * @param startMicros inclusive timestamp in microseconds. - */ - public TimestampRangeFilter startClosed(long startMicros) { - builder.setStartTimestampMicros(startMicros); - return this; + private QualifierRangeFilter( + String family, BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + super(startBound, start, endBound, end); + this.family = Preconditions.checkNotNull(family); } - /** - * Exclusive upper bound. If left empty, interpreted as infinity. - * - * @param endMicros exclusive timestamp in microseconds. - */ - public TimestampRangeFilter endOpen(long endMicros) { - builder.setEndTimestampMicros(endMicros); - return this; + @Override + protected QualifierRangeFilter newInstance( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + return new QualifierRangeFilter(family, startBound, start, endBound, end); } @InternalApi @Override public RowFilter toProto() { - return RowFilter.newBuilder().setTimestampRangeFilter(builder.build()).build(); + ColumnRange.Builder builder = ColumnRange.newBuilder().setFamilyName(family); + + switch (getStartBound()) { + case CLOSED: + builder.setStartQualifierClosed(getStart()); + break; + case OPEN: + builder.setStartQualifierOpen(getStart()); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown start bound: " + getStartBound()); + } + switch (getEndBound()) { + case CLOSED: + builder.setEndQualifierClosed(getEnd()); + break; + case OPEN: + builder.setEndQualifierOpen(getEnd()); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown start bound: " + getStartBound()); + } + + return RowFilter.newBuilder().setColumnRangeFilter(builder.build()).build(); } @Override - protected TimestampRangeFilter clone() { + protected QualifierRangeFilter clone() { try { - TimestampRangeFilter clone = (TimestampRangeFilter) super.clone(); - clone.builder = builder.clone(); - return clone; + return (QualifierRangeFilter) super.clone(); } catch (CloneNotSupportedException | ClassCastException e) { throw new RuntimeException("should never happen"); } @@ -572,98 +501,65 @@ private TimestampFilter() {} public TimestampRangeFilter range() { return new TimestampRangeFilter(); } - - /** - * Matches only cells with timestamps within the given range. - * - * @param startMicros Inclusive start of the range in microseconds. - * @param endMicros Exclusive end of the range in microseconds. - */ - public Filter range(long startMicros, long endMicros) { - return range().startClosed(startMicros).endOpen(endMicros); - } } - /** Matches only cells with values that fall within the given value range. */ - public static final class ValueRangeFilter implements Filter { - private ValueRange.Builder builder; - - private ValueRangeFilter() { - builder = ValueRange.newBuilder(); - } - - /** Used when giving an inclusive lower bound for the range. */ - public ValueRangeFilter startClosed(@Nullable String value) { - return startClosed(wrapString(value)); - } - - /** Used when giving an inclusive lower bound for the range. */ - public ValueRangeFilter startClosed(@Nullable ByteString value) { - if (value == null) { - builder.clearStartValue(); - } else { - builder.setStartValueClosed(value); - } - return this; - } - - /** Used when giving an exclusive lower bound for the range. */ - public ValueRangeFilter startOpen(@Nullable String value) { - return startOpen(wrapString(value)); - } - - /** Used when giving an exclusive lower bound for the range. */ - public ValueRangeFilter startOpen(@Nullable ByteString value) { - if (value == null) { - builder.clearStartValue(); - } else { - builder.setStartValueOpen(value); - } - return this; - } - - /** Used when giving an inclusive upper bound for the range. */ - public ValueRangeFilter endClosed(@Nullable String value) { - return endClosed(wrapString(value)); - } - - /** Used when giving an inclusive upper bound for the range. */ - public ValueRangeFilter endClosed(@Nullable ByteString value) { - if (value == null) { - builder.clearEndValue(); - } else { - builder.setEndValueClosed(value); - } - return this; + /** + * Matches only cells with microsecond timestamps within the given range. Start is inclusive and + * end is exclusive. + */ + public static final class TimestampRangeFilter + extends AbstractTimestampRange implements Filter { + private TimestampRangeFilter() { + super(); } - /** Used when giving an exclusive upper bound for the range. */ - public ValueRangeFilter endOpen(@Nullable String value) { - return endOpen(wrapString(value)); + private TimestampRangeFilter(BoundType startBound, Long start, BoundType endBound, Long end) { + super(startBound, start, endBound, end); } - /** Used when giving an exclusive upper bound for the range. */ - public ValueRangeFilter endOpen(@Nullable ByteString value) { - if (value == null) { - builder.clearEndValue(); - } else { - builder.setEndValueOpen(value); - } - return this; + @Override + protected TimestampRangeFilter newInstance( + BoundType startBound, Long start, BoundType endBound, Long end) { + return new TimestampRangeFilter(startBound, start, endBound, end); } @InternalApi @Override public RowFilter toProto() { - return RowFilter.newBuilder().setValueRangeFilter(builder.build()).build(); + com.google.bigtable.v2.TimestampRange.Builder builder = + com.google.bigtable.v2.TimestampRange.newBuilder(); + + switch (getStartBound()) { + case CLOSED: + builder.setStartTimestampMicros(getStart()); + break; + case OPEN: + builder.setStartTimestampMicros(getStart() + 1); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown start bound: " + getStartBound()); + } + switch (getEndBound()) { + case CLOSED: + builder.setEndTimestampMicros(getEnd() + 1); + break; + case OPEN: + builder.setEndTimestampMicros(getEnd()); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown end bound: " + getEndBound()); + } + return RowFilter.newBuilder().setTimestampRangeFilter(builder.build()).build(); } @Override - public ValueRangeFilter clone() { + protected TimestampRangeFilter clone() { try { - ValueRangeFilter clone = (ValueRangeFilter) super.clone(); - clone.builder = builder.clone(); - return clone; + return (TimestampRangeFilter) super.clone(); } catch (CloneNotSupportedException | ClassCastException e) { throw new RuntimeException("should never happen"); } @@ -711,29 +607,68 @@ public ValueRangeFilter range() { return new ValueRangeFilter(); } - /** - * Construct a {@link ValueRangeFilter} that can create a {@link ValueRange} oriented {@link - * Filter}. - * - * @return a new {@link ValueRangeFilter} - */ - public ValueRangeFilter range(ByteString start, ByteString end) { - return range().startClosed(start).endOpen(end); + /** Replaces each cell's value with the empty string. */ + public Filter strip() { + return STRIP_VALUE; } + } - /** - * Construct a {@link ValueRangeFilter} that can create a {@link ValueRange} oriented {@link - * Filter}. - * - * @return a new {@link ValueRangeFilter} - */ - public ValueRangeFilter range(String start, String end) { - return range().startClosed(start).endOpen(end); + /** Matches only cells with values that fall within the given value range. */ + public static final class ValueRangeFilter extends AbstractByteStringRange + implements Filter { + private ValueRangeFilter() { + super(); } - /** Replaces each cell's value with the empty string. */ - public Filter strip() { - return STRIP_VALUE; + private ValueRangeFilter( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + super(startBound, start, endBound, end); + } + + @Override + protected ValueRangeFilter newInstance( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + return new ValueRangeFilter(startBound, start, endBound, end); + } + + @InternalApi + @Override + public RowFilter toProto() { + ValueRange.Builder builder = ValueRange.newBuilder(); + switch (getStartBound()) { + case CLOSED: + builder.setStartValueClosed(getStart()); + break; + case OPEN: + builder.setStartValueOpen(getStart()); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown start bound: " + getStartBound()); + } + switch (getEndBound()) { + case CLOSED: + builder.setEndValueClosed(getEnd()); + break; + case OPEN: + builder.setEndValueOpen(getEnd()); + break; + case UNBOUNDED: + break; + default: + throw new IllegalStateException("Unknown end bound: " + getEndBound()); + } + return RowFilter.newBuilder().setValueRangeFilter(builder.build()).build(); + } + + @Override + public ValueRangeFilter clone() { + try { + return (ValueRangeFilter) super.clone(); + } catch (CloneNotSupportedException | ClassCastException e) { + throw new RuntimeException("should never happen"); + } } } @@ -774,7 +709,7 @@ public Filter cellsPerColumn(int count) { } private static final class SimpleFilter implements Filter { - private final RowFilter proto; + private RowFilter proto; private SimpleFilter(@Nonnull RowFilter proto) { Preconditions.checkNotNull(proto); @@ -788,8 +723,12 @@ public RowFilter toProto() { } @Override - public Object clone() { - return new SimpleFilter(proto); + public SimpleFilter clone() { + try { + return (SimpleFilter) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("should never happen", e); + } } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java index 64208b04b6ef..2e76b42813ae 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java @@ -20,6 +20,7 @@ import com.google.bigtable.v2.RowRange; import com.google.bigtable.v2.TableName; import com.google.cloud.bigtable.data.v2.internal.RequestContext; +import com.google.cloud.bigtable.data.v2.wrappers.Range.ByteStringRange; import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; @@ -86,14 +87,31 @@ public Query range(ByteString start, ByteString end) { } /** Adds a range to be looked up. */ - public Query range(Range range) { + public Query range(ByteStringRange range) { RowRange.Builder rangeBuilder = RowRange.newBuilder(); - if (range.getStart() != null) { - rangeBuilder.setStartKeyClosed(range.getStart()); + + switch (range.getStartBound()) { + case OPEN: + rangeBuilder.setStartKeyOpen(range.getStart()); + break; + case CLOSED: + rangeBuilder.setStartKeyClosed(range.getStart()); + break; + default: + throw new IllegalStateException("Unknown range bound: " + range.getStartBound()); } - if (range.getEnd() != null) { - rangeBuilder.setEndKeyOpen(range.getEnd()); + + switch (range.getEndBound()) { + case OPEN: + rangeBuilder.setEndKeyOpen(range.getEnd()); + break; + case CLOSED: + rangeBuilder.setEndKeyClosed(range.getEnd()); + break; + default: + throw new IllegalStateException("Unknown range bound: " + range.getEndBound()); } + builder.getRowsBuilder().addRowRanges(rangeBuilder.build()); return this; diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index 0b1c4652d4a1..c19ee1162c17 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -15,67 +15,206 @@ */ package com.google.cloud.bigtable.data.v2.wrappers; +import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; -import javax.annotation.Nullable; /** - * Simple value object to represent a range of values with an inclusive start and an exclusive end. - * Both endpoints can be null to represent positive/negative infinity. + * Range API. + * + *

This base class represents the API for all ranges in the Cloud Bigtable client. */ -public class Range { - @Nullable private final T start; - @Nullable private final T end; +abstract class Range> { + public enum BoundType { + OPEN, + CLOSED, + UNBOUNDED + } - private Range(T start, T end) { + protected final BoundType startBound; + protected final T start; + protected final BoundType endBound; + protected final T end; + + protected Range() { + this(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); + } + + protected Range(BoundType startBound, T start, BoundType endBound, T end) { this.start = start; + this.startBound = startBound; this.end = end; + this.endBound = endBound; } - /** Creates an unbounded {@link Range}. */ - public static Range unbounded() { - return new Range<>(null, null); + /** + * Creates a new {@link Range} with the specified inclusive start and the spcified exclusive end. + */ + public R of(T startClosed, T endOpen) { + return newInstance(BoundType.CLOSED, startClosed, BoundType.OPEN, endOpen); } - /** Creates a bounded {@link Range}. */ - public static Range of(T start, T end) { - return new Range<>(start, end); + /** Creates a new {@link Range} with the specified exclusive start and the current end. */ + public R startOpen(T start) { + return newInstance(BoundType.OPEN, start, endBound, end); } - public static Range ofUtf8ByteStrings(String start, String end) { - ByteString startByteString; - if (start == null) { - startByteString = null; - } else { - startByteString = ByteString.copyFromUtf8(start); - } - - ByteString endByteString; - if (end == null) { - endByteString = null; - } else { - endByteString = ByteString.copyFromUtf8(end); - } + /** Creates a new {@link Range} with the specified inclusive start and the current end. */ + public R startClosed(T start) { + return newInstance(BoundType.CLOSED, start, endBound, end); + } - return of(startByteString, endByteString); + /** Creates a new {@link Range} with the specified exclusive end and the current start. */ + public R endOpen(T end) { + return newInstance(startBound, start, BoundType.OPEN, end); } - /** Creates a new {@link Range} with the specified start and the current end. */ - public Range withStart(T start) { - return new Range<>(start, end); + /** Creates a new {@link Range} with the specified inclusive end and the current start. */ + public R endClosed(T end) { + return newInstance(startBound, start, BoundType.CLOSED, end); } - /** Creates a new {@link Range} with the specified end and the current start. */ - public Range withEnd(T end) { - return new Range<>(start, end); + /** Gets the current start {@link BoundType}. */ + public BoundType getStartBound() { + return startBound; } - /** Gets the start of the {@link Range} (inclusive). */ + /** + * Gets the current start value. + * + * @throws IllegalStateException If the current {@link #getStartBound()} is {@link + * BoundType#UNBOUNDED}. + */ public T getStart() { + Preconditions.checkState(startBound != BoundType.UNBOUNDED, "Start is unbounded"); return start; } - /** Gets the end of the {@link Range} (exclusive). */ + /** Gets the current end {@link BoundType}. */ + public BoundType getEndBound() { + return endBound; + } + + /** + * Gets the current end value. + * + * @throws IllegalStateException If the current {@link #getEndBound()} is {@link + * BoundType#UNBOUNDED}. + */ public T getEnd() { + Preconditions.checkState(endBound != BoundType.UNBOUNDED, "End is unbounded"); return end; } + + /** + * Extension point for subclasses to override. This allows subclasses to maintain chainability. + */ + protected abstract R newInstance(BoundType startBound, T start, BoundType endBound, T end); + + /** Abstract specialization of a {@link Range} for timestamps. */ + abstract static class AbstractTimestampRange> + extends Range { + AbstractTimestampRange() { + super(); + } + + AbstractTimestampRange(BoundType startBound, Long start, BoundType endBound, Long end) { + super(startBound, start, endBound, end); + } + } + + /** + * Abstract specialization of a {@link Range} for {@link ByteString}s. Allows for easy interop + * with simple Strings. + */ + abstract static class AbstractByteStringRange> + extends Range { + AbstractByteStringRange() { + this(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); + } + + AbstractByteStringRange( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + super(startBound, start, endBound, end); + } + + /** + * Creates a new {@link Range} with the specified inclusive start and the spcified exclusive + * end. + */ + public R of(String startClosed, String endOpen) { + return newInstance(BoundType.CLOSED, wrap(startClosed), BoundType.OPEN, wrap(endOpen)); + } + + /** Creates a new {@link Range} with the specified exclusive start and the current end. */ + public R startOpen(String start) { + return newInstance(BoundType.OPEN, wrap(start), endBound, end); + } + + /** Creates a new {@link Range} with the specified inclusive start and the current end. */ + public R startClosed(String start) { + return newInstance(BoundType.CLOSED, wrap(start), endBound, end); + } + + /** Creates a new {@link Range} with the specified exclusive end and the current start. */ + public R endOpen(String end) { + return newInstance(getStartBound(), start, BoundType.OPEN, wrap(end)); + } + + /** Creates a new {@link Range} with the specified inclusive end and the current start. */ + public R endClosed(String end) { + return newInstance(startBound, start, BoundType.CLOSED, wrap(end)); + } + + static ByteString wrap(String str) { + return ByteString.copyFromUtf8(str); + } + } + + /** Concrete Range for timestamps */ + public static class TimestampRange extends AbstractTimestampRange { + public static TimestampRange unbounded() { + return new TimestampRange(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); + } + + public static TimestampRange create(long closedStart, long openEnd) { + return new TimestampRange(BoundType.CLOSED, closedStart, BoundType.OPEN, openEnd); + } + + private TimestampRange(BoundType startBound, Long start, BoundType endBound, Long end) { + super(startBound, start, endBound, end); + } + + @Override + protected TimestampRange newInstance( + BoundType startBound, Long start, BoundType endBound, Long end) { + return new TimestampRange(startBound, start, endBound, end); + } + } + + /** Concrete Range for ByteStrings */ + public static class ByteStringRange extends AbstractByteStringRange { + public static ByteStringRange unbounded() { + return new ByteStringRange(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); + } + + public static ByteStringRange create(ByteString closedStart, ByteString openEnd) { + return new ByteStringRange(BoundType.CLOSED, closedStart, BoundType.OPEN, openEnd); + } + + public static ByteStringRange create(String closedStart, String openEnd) { + return new ByteStringRange( + BoundType.CLOSED, wrap(closedStart), BoundType.OPEN, wrap(openEnd)); + } + + private ByteStringRange( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + super(startBound, start, endBound, end); + } + + @Override + protected ByteStringRange newInstance( + BoundType startBound, ByteString start, BoundType endBound, ByteString end) { + return new ByteStringRange(startBound, start, endBound, end); + } + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java index e3e8c42b04f3..55444831b18d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java @@ -48,7 +48,7 @@ public static Row create(ByteString key, List cells) { public int compareTo(@Nonnull Row row) { int sizeA = key().size(); int sizeB = row.key().size(); - int size = Math.min(sizeA, sizeB); + int size = Math.min(sizeA, sizeB); for (int i = 0; i < size; i++) { int byteA = key().byteAt(i) & 0xff; diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java index b59e6fac6eb5..95fa579cc5a7 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java @@ -23,6 +23,7 @@ import com.google.bigtable.v2.RowFilter.Chain; import com.google.bigtable.v2.RowFilter.Condition; import com.google.bigtable.v2.RowFilter.Interleave; +import com.google.bigtable.v2.TimestampRange; import com.google.bigtable.v2.ValueRange; import com.google.protobuf.ByteString; import org.junit.Test; @@ -114,43 +115,17 @@ public void conditionTest() { } @Test - public void passTest() { - RowFilter actualFilter = FILTERS.pass().toProto(); - - RowFilter expectedFilter = RowFilter.newBuilder().setPassAllFilter(true).build(); - - assertThat(actualFilter).isEqualTo(expectedFilter); - } - - @Test - public void blockTest() { - RowFilter actualFilter = FILTERS.block().toProto(); - - RowFilter expectedFilter = RowFilter.newBuilder().setBlockAllFilter(true).build(); - - assertThat(actualFilter).isEqualTo(expectedFilter); - } - - @Test - public void sinkTest() { - RowFilter actualFilter = FILTERS.sink().toProto(); - - RowFilter expectedFilter = RowFilter.newBuilder().setSink(true).build(); - - assertThat(actualFilter).isEqualTo(expectedFilter); - } - - @Test - public void labelTest() { - RowFilter actualFilter = FILTERS.label("my-label").toProto(); + public void keyRegexTest() { + RowFilter actualFilter = FILTERS.key().regex(ByteString.copyFromUtf8(".*")).toProto(); - RowFilter expectedFilter = RowFilter.newBuilder().setApplyLabelTransformer("my-label").build(); + RowFilter expectedFilter = + RowFilter.newBuilder().setRowKeyRegexFilter(ByteString.copyFromUtf8(".*")).build(); assertThat(actualFilter).isEqualTo(expectedFilter); } @Test - public void keyRegexTest() { + public void keyRegexStringTest() { RowFilter actualFilter = FILTERS.key().regex(".*").toProto(); RowFilter expectedFilter = @@ -198,6 +173,18 @@ public void familyExactMatchTest() { @Test public void qualifierRegexTest() { + RowFilter actualFilter = FILTERS.qualifier().regex(ByteString.copyFromUtf8("^hi")).toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setColumnQualifierRegexFilter(ByteString.copyFromUtf8("^hi")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void qualifierRegexStringTest() { RowFilter actualFilter = FILTERS.qualifier().regex("^hi").toProto(); RowFilter expectedFilter = @@ -222,7 +209,7 @@ public void qualifierExactMatchTest() { } @Test - public void qualifierRangeSimple() { + public void qualifierRangeInFamilyClosedOpen() { RowFilter actualFilter = FILTERS .qualifier() @@ -244,13 +231,13 @@ public void qualifierRangeSimple() { } @Test - public void qualifierRangeByte() { + public void qualifierRangeInFamilyOpenClosed() { RowFilter actualFilter = FILTERS .qualifier() .rangeWithinFamily("family") - .startClosed("begin") - .endOpen("end") + .startOpen("begin") + .endClosed("end") .toProto(); RowFilter expectedFilter = @@ -258,8 +245,8 @@ public void qualifierRangeByte() { .setColumnRangeFilter( ColumnRange.newBuilder() .setFamilyName("family") - .setStartQualifierClosed(ByteString.copyFromUtf8("begin")) - .setEndQualifierOpen(ByteString.copyFromUtf8("end"))) + .setStartQualifierOpen(ByteString.copyFromUtf8("begin")) + .setEndQualifierClosed(ByteString.copyFromUtf8("end"))) .build(); assertThat(actualFilter).isEqualTo(expectedFilter); @@ -288,52 +275,69 @@ public void qualifierRangeRange() { } @Test - public void valueRegex() { - RowFilter actualFilter = FILTERS.value().regex("some[0-9]regex").toProto(); + public void timestampRange() { + RowFilter actualFilter = + FILTERS.timestamp().range().startClosed(1_000L).endOpen(30_000L).toProto(); RowFilter expectedFilter = RowFilter.newBuilder() - .setValueRegexFilter(ByteString.copyFromUtf8("some[0-9]regex")) + .setTimestampRangeFilter( + TimestampRange.newBuilder() + .setStartTimestampMicros(1_000L) + .setEndTimestampMicros(30_000L)) .build(); assertThat(actualFilter).isEqualTo(expectedFilter); } @Test - public void valueExactMatch() { + public void timestampOpenClosedFakeRange() { RowFilter actualFilter = - FILTERS.value().exactMatch(ByteString.copyFromUtf8("some[0-9]regex")).toProto(); + FILTERS.timestamp().range().startOpen(1_000L).endClosed(30_000L).toProto(); + // open start & closed end are faked in the client by incrementing the query RowFilter expectedFilter = RowFilter.newBuilder() - .setValueRegexFilter(ByteString.copyFromUtf8("some\\[0\\-9\\]regex")) + .setTimestampRangeFilter( + TimestampRange.newBuilder() + .setStartTimestampMicros(1_000L + 1) + .setEndTimestampMicros(30_000L + 1)) .build(); assertThat(actualFilter).isEqualTo(expectedFilter); } @Test - public void valueRangeSimple() { - RowFilter actualFilter = FILTERS.value().range("begin", "end").toProto(); + public void valueRegex() { + RowFilter actualFilter = FILTERS.value().regex("some[0-9]regex").toProto(); RowFilter expectedFilter = RowFilter.newBuilder() - .setValueRangeFilter( - ValueRange.newBuilder() - .setStartValueClosed(ByteString.copyFromUtf8("begin")) - .setEndValueOpen(ByteString.copyFromUtf8("end"))) + .setValueRegexFilter(ByteString.copyFromUtf8("some[0-9]regex")) .build(); assertThat(actualFilter).isEqualTo(expectedFilter); } @Test - public void valueRangeByte() { + public void valueExactMatch() { RowFilter actualFilter = - FILTERS - .value() - .range(ByteString.copyFromUtf8("begin"), ByteString.copyFromUtf8("end")) - .toProto(); + FILTERS.value().exactMatch(ByteString.copyFromUtf8("some[0-9]regex")).toProto(); + + RowFilter expectedFilter = + RowFilter.newBuilder() + .setValueRegexFilter(ByteString.copyFromUtf8("some\\[0\\-9\\]regex")) + .build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void valueRangeClosedOpen() { + RowFilter actualFilter = FILTERS.value().range() + .startClosed("begin") + .endOpen("end") + .toProto(); RowFilter expectedFilter = RowFilter.newBuilder() @@ -347,15 +351,18 @@ public void valueRangeByte() { } @Test - public void valueRangeRange() { - RowFilter actualFilter = FILTERS.value().range().startClosed("begin").endOpen("end").toProto(); + public void valueRangeOpenClosed() { + RowFilter actualFilter = FILTERS.value().range() + .startOpen("begin") + .endClosed("end") + .toProto(); RowFilter expectedFilter = RowFilter.newBuilder() .setValueRangeFilter( ValueRange.newBuilder() - .setStartValueClosed(ByteString.copyFromUtf8("begin")) - .setEndValueOpen(ByteString.copyFromUtf8("end"))) + .setStartValueOpen(ByteString.copyFromUtf8("begin")) + .setEndValueClosed(ByteString.copyFromUtf8("end"))) .build(); assertThat(actualFilter).isEqualTo(expectedFilter); @@ -396,4 +403,40 @@ public void limitCellsPerColumnTest() { assertThat(actualFilter).isEqualTo(expectedFilter); } + + @Test + public void passTest() { + RowFilter actualFilter = FILTERS.pass().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setPassAllFilter(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void blockTest() { + RowFilter actualFilter = FILTERS.block().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setBlockAllFilter(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void sinkTest() { + RowFilter actualFilter = FILTERS.sink().toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setSink(true).build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } + + @Test + public void labelTest() { + RowFilter actualFilter = FILTERS.label("my-label").toProto(); + + RowFilter expectedFilter = RowFilter.newBuilder().setApplyLabelTransformer("my-label").build(); + + assertThat(actualFilter).isEqualTo(expectedFilter); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java index 9e284fcc100f..9e3da96165e7 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/QueryTest.java @@ -25,6 +25,7 @@ import com.google.bigtable.v2.RowRange; import com.google.bigtable.v2.TableName; import com.google.cloud.bigtable.data.v2.internal.RequestContext; +import com.google.cloud.bigtable.data.v2.wrappers.Range.ByteStringRange; import com.google.protobuf.ByteString; import org.junit.Before; import org.junit.Test; @@ -76,7 +77,7 @@ public void rowRangeTest() { Query.create(TABLE_NAME.getTable()) .range("simple-begin", "simple-end") .range(ByteString.copyFromUtf8("byte-begin"), ByteString.copyFromUtf8("byte-end")) - .range(Range.ofUtf8ByteStrings("range-begin", "range-end")); + .range(ByteStringRange.create("range-begin", "range-end")); Builder expectedProto = expectedProtoBuilder(); expectedProto diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java index fdb0507ba29c..6bd2f474b601 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java @@ -17,35 +17,186 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.cloud.bigtable.data.v2.wrappers.Range.BoundType; +import com.google.cloud.bigtable.data.v2.wrappers.Range.ByteStringRange; +import com.google.cloud.bigtable.data.v2.wrappers.Range.TimestampRange; +import com.google.common.truth.DefaultSubject; +import com.google.common.truth.Subject; import com.google.protobuf.ByteString; import org.junit.Test; +import org.junit.internal.Throwables; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class RangeTest { @Test - public void utf8StringsTest() { - Range range = Range.ofUtf8ByteStrings("begin", "end"); + public void timestampUnboundedTest() { + TimestampRange range = TimestampRange.unbounded(); + assertThat(range.getStartBound()).isEqualTo(BoundType.UNBOUNDED); + assertThat(range.getEndBound()).isEqualTo(BoundType.UNBOUNDED); - assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("begin")); - assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("end")); + Throwable actualError = null; + try { + //noinspection ResultOfMethodCallIgnored + range.getStart(); + } catch (Throwable e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(IllegalStateException.class); + + try { + //noinspection ResultOfMethodCallIgnored + range.getEnd(); + } catch (Throwable e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(IllegalStateException.class); + } + + @Test + public void timestampOfTest() { + TimestampRange range = TimestampRange.create(10, 2_000); + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(10); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(2_000); } @Test - public void changeStartTest() { - Range range = - Range.ofUtf8ByteStrings("begin", "end").withStart(ByteString.copyFromUtf8("begin2")); + public void timestampChangeStartTest() { + TimestampRange range = TimestampRange.create(10, 2_000).startOpen(20L); + + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(2_000); - assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("begin2")); - assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("end")); + assertThat(range.getStartBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getStart()).isEqualTo(20); + + range = range.startClosed(30L); + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(30); } @Test - public void changeEndTest() { - Range range = Range.of(10, 20).withEnd(30); + public void timestampChangeEndTest() { + TimestampRange range = TimestampRange.create(10, 2_000).endClosed(1_000L); + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); assertThat(range.getStart()).isEqualTo(10); - assertThat(range.getEnd()).isEqualTo(30); + + assertThat(range.getEndBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getEnd()).isEqualTo(1_000); + + range = range.endOpen(3_000L); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(3_000); + } + + @Test + public void byteStringUnboundedTest() { + ByteStringRange range = ByteStringRange.unbounded(); + assertThat(range.getStartBound()).isEqualTo(BoundType.UNBOUNDED); + assertThat(range.getEndBound()).isEqualTo(BoundType.UNBOUNDED); + + Throwable actualError = null; + try { + range.getStart(); + } catch (Throwable e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(IllegalStateException.class); + + try { + range.getEnd(); + } catch (Throwable e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(IllegalStateException.class); + } + + @Test + public void byteStringOfTest() { + ByteStringRange range = + ByteStringRange.create(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("b")); + + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("a")); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("b")); + } + + @Test + public void byteStringOfStringTest() { + ByteStringRange range = ByteStringRange.create("a", "b"); + + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("a")); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("b")); + } + + @Test + public void byteStringChangeStartTest() { + ByteStringRange range = + ByteStringRange.create(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("z")) + .startOpen(ByteString.copyFromUtf8("b")); + + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("z")); + + assertThat(range.getStartBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("b")); + + range = range.startClosed(ByteString.copyFromUtf8("c")); + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("c")); + } + + @Test + public void byteStringChangeStartStringTest() { + ByteStringRange range = ByteStringRange.create("a", "z").startOpen("b"); + + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("z")); + + assertThat(range.getStartBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("b")); + + range = range.startClosed("c"); + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("c")); + } + + @Test + public void byteStringChangeEndTest() { + ByteStringRange range = + ByteStringRange.create(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("z")) + .endClosed(ByteString.copyFromUtf8("y")); + + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("a")); + + assertThat(range.getEndBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("y")); + + range = range.endOpen(ByteString.copyFromUtf8("x")); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("x")); + } + + @Test + public void byteStringChangeEndStringTest() { + ByteStringRange range = ByteStringRange.create("a", "z").endClosed("y"); + + assertThat(range.getStartBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getStart()).isEqualTo(ByteString.copyFromUtf8("a")); + + assertThat(range.getEndBound()).isEqualTo(BoundType.CLOSED); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("y")); + + range = range.endOpen("x"); + assertThat(range.getEndBound()).isEqualTo(BoundType.OPEN); + assertThat(range.getEnd()).isEqualTo(ByteString.copyFromUtf8("x")); } } From 9b46ae4908eec833f6ca6b26696ba67fd4536460 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 5 Feb 2018 20:45:10 -0500 Subject: [PATCH 04/18] tweaks --- .../cloud/bigtable/data/v2/wrappers/Range.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index c19ee1162c17..fc893a307909 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -108,7 +108,7 @@ public T getEnd() { /** * Extension point for subclasses to override. This allows subclasses to maintain chainability. */ - protected abstract R newInstance(BoundType startBound, T start, BoundType endBound, T end); + abstract R newInstance(BoundType startBound, T start, BoundType endBound, T end); /** Abstract specialization of a {@link Range} for timestamps. */ abstract static class AbstractTimestampRange> @@ -157,7 +157,7 @@ public R startClosed(String start) { /** Creates a new {@link Range} with the specified exclusive end and the current start. */ public R endOpen(String end) { - return newInstance(getStartBound(), start, BoundType.OPEN, wrap(end)); + return newInstance(startBound, start, BoundType.OPEN, wrap(end)); } /** Creates a new {@link Range} with the specified inclusive end and the current start. */ @@ -171,7 +171,7 @@ static ByteString wrap(String str) { } /** Concrete Range for timestamps */ - public static class TimestampRange extends AbstractTimestampRange { + public static final class TimestampRange extends AbstractTimestampRange { public static TimestampRange unbounded() { return new TimestampRange(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); } @@ -185,14 +185,14 @@ private TimestampRange(BoundType startBound, Long start, BoundType endBound, Lon } @Override - protected TimestampRange newInstance( + TimestampRange newInstance( BoundType startBound, Long start, BoundType endBound, Long end) { return new TimestampRange(startBound, start, endBound, end); } } /** Concrete Range for ByteStrings */ - public static class ByteStringRange extends AbstractByteStringRange { + public static final class ByteStringRange extends AbstractByteStringRange { public static ByteStringRange unbounded() { return new ByteStringRange(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); } @@ -212,7 +212,7 @@ private ByteStringRange( } @Override - protected ByteStringRange newInstance( + ByteStringRange newInstance( BoundType startBound, ByteString start, BoundType endBound, ByteString end) { return new ByteStringRange(startBound, start, endBound, end); } From fa5aafcdedcd62715155f2607c5c0d00f9a05bf5 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 11:15:42 -0500 Subject: [PATCH 05/18] Truth static import --- .../cloud/bigtable/data/v2/BigtableDataClientTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 4ad3f212668e..411ca1b86ac5 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 @@ -15,12 +15,13 @@ */ package com.google.cloud.bigtable.data.v2; +import static com.google.common.truth.Truth.assertThat; + import com.google.api.gax.rpc.ResponseObserver; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; import com.google.cloud.bigtable.data.v2.wrappers.Query; import com.google.cloud.bigtable.data.v2.wrappers.Row; -import com.google.common.truth.Truth; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,7 +50,7 @@ public void proxyCloseTest() throws Exception { @Test public void proxyReadRowsCallableTest() { - Truth.assertThat(bigtableDataClient.readRowsCallable()).isSameAs(mockReadRowsCallable); + assertThat(bigtableDataClient.readRowsCallable()).isSameAs(mockReadRowsCallable); } @Test From 88da7de7a54203d59b42f56ecbe86e8c560b6272 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 11:17:55 -0500 Subject: [PATCH 06/18] cleanup imports --- .../cloud/bigtable/data/v2/internal/RegexUtilTest.java | 9 +++++---- .../cloud/bigtable/data/v2/wrappers/RangeTest.java | 3 --- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java index 9f7df83c598f..befd4281d295 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/RegexUtilTest.java @@ -15,7 +15,8 @@ */ package com.google.cloud.bigtable.data.v2.internal; -import com.google.common.truth.Truth; +import static com.google.common.truth.Truth.assertThat; + import com.google.protobuf.ByteString; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,7 +29,7 @@ public void literalRegexPassthroughTest() { ByteString input = ByteString.copyFromUtf8("hi"); ByteString actual = RegexUtil.literalRegex(input); - Truth.assertThat(actual).isEqualTo(input); + assertThat(actual).isEqualTo(input); } @Test @@ -37,7 +38,7 @@ public void literalRegexEscapeTest() { ByteString actual = RegexUtil.literalRegex(input); ByteString expected = ByteString.copyFromUtf8("h\\.\\*i"); - Truth.assertThat(actual).isEqualTo(expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -45,6 +46,6 @@ public void literalRegexEscapeBytes() { ByteString input = ByteString.copyFrom(new byte[] {(byte) 0xe2, (byte) 0x80, (byte) 0xb3}); ByteString actual = RegexUtil.literalRegex(input); - Truth.assertThat(actual).isEqualTo(input); + assertThat(actual).isEqualTo(input); } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java index 6bd2f474b601..e42542f32788 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RangeTest.java @@ -20,11 +20,8 @@ import com.google.cloud.bigtable.data.v2.wrappers.Range.BoundType; import com.google.cloud.bigtable.data.v2.wrappers.Range.ByteStringRange; import com.google.cloud.bigtable.data.v2.wrappers.Range.TimestampRange; -import com.google.common.truth.DefaultSubject; -import com.google.common.truth.Subject; import com.google.protobuf.ByteString; import org.junit.Test; -import org.junit.internal.Throwables; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; From e3684fbf2fdd2152a6fc2926e6b7f6826758a68d Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 11:19:11 -0500 Subject: [PATCH 07/18] reformat --- .../google/cloud/bigtable/data/v2/wrappers/Range.java | 3 +-- .../cloud/bigtable/data/v2/wrappers/FiltersTest.java | 10 ++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index fc893a307909..a144eb06b4de 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -185,8 +185,7 @@ private TimestampRange(BoundType startBound, Long start, BoundType endBound, Lon } @Override - TimestampRange newInstance( - BoundType startBound, Long start, BoundType endBound, Long end) { + TimestampRange newInstance(BoundType startBound, Long start, BoundType endBound, Long end) { return new TimestampRange(startBound, start, endBound, end); } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java index 95fa579cc5a7..1aa66f9200f2 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/FiltersTest.java @@ -334,10 +334,7 @@ public void valueExactMatch() { @Test public void valueRangeClosedOpen() { - RowFilter actualFilter = FILTERS.value().range() - .startClosed("begin") - .endOpen("end") - .toProto(); + RowFilter actualFilter = FILTERS.value().range().startClosed("begin").endOpen("end").toProto(); RowFilter expectedFilter = RowFilter.newBuilder() @@ -352,10 +349,7 @@ public void valueRangeClosedOpen() { @Test public void valueRangeOpenClosed() { - RowFilter actualFilter = FILTERS.value().range() - .startOpen("begin") - .endClosed("end") - .toProto(); + RowFilter actualFilter = FILTERS.value().range().startOpen("begin").endClosed("end").toProto(); RowFilter expectedFilter = RowFilter.newBuilder() From 90cc1278c08c88909e5cf138328c19937bfb6ade Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 19:07:09 -0500 Subject: [PATCH 08/18] remove placeholder --- .../data/v2/BigtableDataSettings.java | 6 +- .../v2/stub/EnhancedBigtableStubSettings.java | 12 +- ...laceholderServerStreamingCallSettings.java | 165 ------------------ .../EnhancedBigtableStubSettingsTest.java | 4 +- 4 files changed, 11 insertions(+), 176 deletions(-) delete mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java index aa59eb8e8982..4926bb0e5221 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java @@ -16,11 +16,11 @@ package com.google.cloud.bigtable.data.v2; import com.google.api.gax.rpc.ClientSettings; +import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; import com.google.cloud.bigtable.data.v2.wrappers.Query; import com.google.cloud.bigtable.data.v2.wrappers.Row; -import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; import java.io.IOException; import javax.annotation.Nonnull; @@ -71,7 +71,7 @@ public String getAppProfileId() { } /** Returns the object with the settings used for calls to ReadRows. */ - public PlaceholderServerStreamingCallSettings readRowsSettings() { + public ServerStreamingCallSettings readRowsSettings() { return getTypedStubSettings().readRowsSettings(); } @@ -136,7 +136,7 @@ public String getAppProfileId() { } /** Returns the builder for the settings used for calls to readRows. */ - public PlaceholderServerStreamingCallSettings.Builder readRowsSettings() { + public ServerStreamingCallSettings.Builder readRowsSettings() { return getTypedStubSettings().readRowsSettings(); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index 28475c15792c..72f8bfeddbf1 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -18,13 +18,13 @@ import com.google.api.core.InternalApi; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.StubSettings; import com.google.api.gax.rpc.UnaryCallSettings; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.wrappers.Query; import com.google.cloud.bigtable.data.v2.wrappers.Row; -import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; import com.google.common.base.Preconditions; import javax.annotation.Nonnull; import org.threeten.bp.Duration; @@ -68,7 +68,7 @@ public class EnhancedBigtableStubSettings extends StubSettings readRowsSettings; + private final ServerStreamingCallSettings readRowsSettings; private EnhancedBigtableStubSettings(Builder builder) { super(builder); @@ -95,7 +95,7 @@ public String getAppProfileId() { } /** Returns the object with the settings used for calls to ReadRows. */ - public PlaceholderServerStreamingCallSettings readRowsSettings() { + public ServerStreamingCallSettings readRowsSettings() { return readRowsSettings; } @@ -109,7 +109,7 @@ public static class Builder extends StubSettings.Builder readRowsSettings; + private final ServerStreamingCallSettings.Builder readRowsSettings; /** * Initializes a new Builder with sane defaults for all settings. @@ -135,7 +135,7 @@ private Builder() { .build()); // Per-method settings using baseSettings for defaults. - readRowsSettings = PlaceholderServerStreamingCallSettings.newBuilder(); + readRowsSettings = ServerStreamingCallSettings.newBuilder(); /* TODO: copy timeouts, retryCodes & retrySettings from baseSettings.readRows once it exists in GAPIC */ readRowsSettings .setRetryableCodes(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE, Code.ABORTED) @@ -209,7 +209,7 @@ public String getAppProfileId() { } /** Returns the builder for the settings used for calls to readRows. */ - public PlaceholderServerStreamingCallSettings.Builder readRowsSettings() { + public ServerStreamingCallSettings.Builder readRowsSettings() { return readRowsSettings; } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java deleted file mode 100644 index 55e3c5180d75..000000000000 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/PlaceholderServerStreamingCallSettings.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.cloud.bigtable.gaxx; - -import com.google.api.gax.retrying.RetrySettings; -import com.google.api.gax.rpc.StatusCode; -import com.google.api.gax.rpc.StatusCode.Code; -import com.google.api.gax.rpc.StreamingCallSettings; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; -import java.util.Set; -import org.threeten.bp.Duration; - -/** - * Placeholder for code in the unmerged PR: https://github.com/googleapis/gax-java/pull/463 - * - *

This class will be replaced by the ServerStreamingCallSettings defined there. - */ -public final class PlaceholderServerStreamingCallSettings - extends StreamingCallSettings { - - private final Set retryableCodes; - private final RetrySettings retrySettings; - - private final Duration timeoutCheckInterval; - private final Duration idleTimeout; - - private PlaceholderServerStreamingCallSettings(Builder builder) { - this.retryableCodes = ImmutableSet.copyOf(builder.retryableCodes); - this.retrySettings = builder.retrySettings; - this.timeoutCheckInterval = builder.timeoutCheckInterval; - this.idleTimeout = builder.idleTimeout; - } - - public Set getRetryableCodes() { - return retryableCodes; - } - - public RetrySettings getRetrySettings() { - return retrySettings; - } - - public Duration getTimeoutCheckInterval() { - return timeoutCheckInterval; - } - - public Duration getIdleTimeout() { - return idleTimeout; - } - - public Builder toBuilder() { - return new Builder<>(this); - } - - public static Builder newBuilder() { - return new Builder<>(); - } - - public static class Builder - extends StreamingCallSettings.Builder { - private Set retryableCodes; - private RetrySettings retrySettings; - - private Duration timeoutCheckInterval; - private Duration idleTimeout; - - private Builder() { - this.retryableCodes = ImmutableSet.of(); - this.retrySettings = RetrySettings.newBuilder().build(); - - this.timeoutCheckInterval = Duration.ZERO; - this.idleTimeout = Duration.ZERO; - } - - private Builder(PlaceholderServerStreamingCallSettings settings) { - super(settings); - this.retryableCodes = settings.retryableCodes; - this.retrySettings = settings.retrySettings; - - this.timeoutCheckInterval = settings.timeoutCheckInterval; - this.idleTimeout = settings.idleTimeout; - } - - public Builder setRetryableCodes(StatusCode.Code... codes) { - this.setRetryableCodes(Sets.newHashSet(codes)); - return this; - } - - public Builder setRetryableCodes(Set retryableCodes) { - this.retryableCodes = Sets.newHashSet(retryableCodes); - return this; - } - - public Set getRetryableCodes() { - return retryableCodes; - } - - public Builder setRetrySettings(RetrySettings retrySettings) { - this.retrySettings = retrySettings; - return this; - } - - public RetrySettings getRetrySettings() { - return retrySettings; - } - - public Builder setSimpleTimeoutNoRetries(Duration timeout) { - setRetryableCodes(); - setRetrySettings( - RetrySettings.newBuilder() - .setTotalTimeout(timeout) - .setInitialRetryDelay(Duration.ZERO) - .setRetryDelayMultiplier(1) - .setMaxRetryDelay(Duration.ZERO) - .setInitialRpcTimeout(timeout) - .setRpcTimeoutMultiplier(1) - .setMaxRpcTimeout(timeout) - .setMaxAttempts(1) - .build()); - - // enable watchdog - Duration checkInterval = Ordering.natural().max(timeout.dividedBy(2), Duration.ofSeconds(10)); - setTimeoutCheckInterval(checkInterval); - - return this; - } - - public Duration getTimeoutCheckInterval() { - return timeoutCheckInterval; - } - - public Builder setTimeoutCheckInterval(Duration timeoutCheckInterval) { - this.timeoutCheckInterval = timeoutCheckInterval; - return this; - } - - public Duration getIdleTimeout() { - return idleTimeout; - } - - public Builder setIdleTimeout(Duration idleTimeout) { - this.idleTimeout = idleTimeout; - return this; - } - - @Override - public PlaceholderServerStreamingCallSettings build() { - return new PlaceholderServerStreamingCallSettings<>(this); - } - } -} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java index 5b8ebfd4041a..ce76d5e8052d 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java @@ -20,12 +20,12 @@ import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StatusCode.Code; import com.google.bigtable.admin.v2.InstanceName; import com.google.cloud.bigtable.data.v2.BigtableDataSettings; import com.google.cloud.bigtable.data.v2.wrappers.Query; import com.google.cloud.bigtable.data.v2.wrappers.Row; -import com.google.cloud.bigtable.gaxx.PlaceholderServerStreamingCallSettings; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -158,7 +158,7 @@ public void readRowsIsNotLostTest() throws IOException { @Test public void readRowsHasSaneDefaultsTest() { - PlaceholderServerStreamingCallSettings.Builder builder = + ServerStreamingCallSettings.Builder builder = EnhancedBigtableStubSettings.newBuilder().readRowsSettings(); assertThat(builder.getTimeoutCheckInterval()).isGreaterThan(Duration.ZERO); From 0e8ff21369f567c5c0aa210bc3a8e31cfc2f5f26 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 19:11:52 -0500 Subject: [PATCH 09/18] fix visibility of clone --- .../google/cloud/bigtable/data/v2/wrappers/Filters.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index 6989dcda881d..755ebf8e4f6c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -253,7 +253,7 @@ public RowFilter toProto() { } @Override - protected InterleaveFilter clone() { + public InterleaveFilter clone() { try { InterleaveFilter clone = (InterleaveFilter) super.clone(); clone.builder = builder.clone(); @@ -297,7 +297,7 @@ public RowFilter toProto() { } @Override - protected ConditionFilter clone() { + public ConditionFilter clone() { try { ConditionFilter clone = (ConditionFilter) super.clone(); clone.builder = builder.clone(); @@ -481,7 +481,7 @@ public RowFilter toProto() { } @Override - protected QualifierRangeFilter clone() { + public QualifierRangeFilter clone() { try { return (QualifierRangeFilter) super.clone(); } catch (CloneNotSupportedException | ClassCastException e) { @@ -557,7 +557,7 @@ public RowFilter toProto() { } @Override - protected TimestampRangeFilter clone() { + public TimestampRangeFilter clone() { try { return (TimestampRangeFilter) super.clone(); } catch (CloneNotSupportedException | ClassCastException e) { From db00235015333529692e54a88a76b21e237e5525 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 19:13:41 -0500 Subject: [PATCH 10/18] tweaks --- .../com/google/cloud/bigtable/data/v2/wrappers/Filters.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index 755ebf8e4f6c..64b1ce79698b 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -428,7 +428,7 @@ public QualifierRangeFilter rangeWithinFamily(@Nonnull String family) { /** Matches only cells from columns within the given range. */ public static final class QualifierRangeFilter extends AbstractByteStringRange implements Filter { - private @Nonnull String family; + private final String family; private QualifierRangeFilter(String family) { super(); @@ -709,7 +709,7 @@ public Filter cellsPerColumn(int count) { } private static final class SimpleFilter implements Filter { - private RowFilter proto; + private final RowFilter proto; private SimpleFilter(@Nonnull RowFilter proto) { Preconditions.checkNotNull(proto); From 45c7b0546eb6705a4ee899bbdb49961177829a55 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 8 Feb 2018 19:30:56 -0500 Subject: [PATCH 11/18] tweaks --- .../cloud/bigtable/data/v2/wrappers/Range.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index a144eb06b4de..6f617077f2be 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -30,16 +30,16 @@ public enum BoundType { UNBOUNDED } - protected final BoundType startBound; - protected final T start; - protected final BoundType endBound; - protected final T end; + final BoundType startBound; + final T start; + final BoundType endBound; + final T end; - protected Range() { + Range() { this(BoundType.UNBOUNDED, null, BoundType.UNBOUNDED, null); } - protected Range(BoundType startBound, T start, BoundType endBound, T end) { + Range(BoundType startBound, T start, BoundType endBound, T end) { this.start = start; this.startBound = startBound; this.end = end; @@ -47,7 +47,7 @@ protected Range(BoundType startBound, T start, BoundType endBound, T end) { } /** - * Creates a new {@link Range} with the specified inclusive start and the spcified exclusive end. + * Creates a new {@link Range} with the specified inclusive start and the specified exclusive end. */ public R of(T startClosed, T endOpen) { return newInstance(BoundType.CLOSED, startClosed, BoundType.OPEN, endOpen); @@ -138,7 +138,7 @@ abstract static class AbstractByteStringRange Date: Fri, 9 Feb 2018 10:44:07 -0500 Subject: [PATCH 12/18] address feedback --- .../bigtable/data/v2/BigtableDataClient.java | 2 +- .../data/v2/wrappers/DefaultRowAdapter.java | 11 ++-- .../bigtable/data/v2/wrappers/Filters.java | 4 +- .../bigtable/data/v2/wrappers/Query.java | 19 ++++-- .../bigtable/data/v2/wrappers/Range.java | 59 +++++++++++++++---- .../cloud/bigtable/data/v2/wrappers/Row.java | 53 +++-------------- .../bigtable/data/v2/wrappers/RowAdapter.java | 4 +- .../bigtable/data/v2/wrappers/RowCell.java | 57 ++++++++++++++++++ .../v2/wrappers/DefaultRowAdapterTest.java | 9 ++- .../bigtable/data/v2/wrappers/RowTest.java | 7 +-- 10 files changed, 140 insertions(+), 85 deletions(-) create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowCell.java 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 5a4bb8f08f84..2f17f146546e 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 @@ -208,7 +208,7 @@ public void readRowsAsync(Query query, ResponseObserver observer) { * // Do something with row * } * - * // Point look ups + * // Point look up * ApiFuture rowFuture = bigtableClient.readRowsCallable().first().futureCall(query); * * // etc diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java index 867d58cf3ac5..2bfcf688a4f8 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapter.java @@ -15,7 +15,6 @@ */ package com.google.cloud.bigtable.data.v2.wrappers; -import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; import java.util.List; @@ -27,7 +26,7 @@ public class DefaultRowAdapter implements RowAdapter { /** {@inheritDoc} */ @Override public boolean isScanMarkerRow(Row row) { - return row.cells().isEmpty(); + return row.getCells().isEmpty(); } /** {@inheritDoc} */ @@ -39,13 +38,13 @@ public RowBuilder createRowBuilder() { /** {@inheritDoc} */ @Override public ByteString getKey(Row row) { - return row.key(); + return row.getKey(); } /** {@inheritDoc} */ public class DefaultRowBuilder implements RowBuilder { private ByteString currentKey; - private ImmutableList.Builder cells; + private ImmutableList.Builder cells; private String family; private ByteString qualifier; private List labels; @@ -55,7 +54,7 @@ public class DefaultRowBuilder implements RowBuilder { /** {@inheritDoc} */ @Override public Row createScanMarkerRow(ByteString key) { - return Row.create(key, ImmutableList.of()); + return Row.create(key, ImmutableList.of()); } /** {@inheritDoc} */ @@ -85,7 +84,7 @@ public void cellValue(ByteString value) { /** {@inheritDoc} */ @Override public void finishCell() { - cells.add(Cell.create(family, qualifier, timestamp, labels, value)); + cells.add(RowCell.create(family, qualifier, timestamp, labels, value)); } /** {@inheritDoc} */ diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index 64b1ce79698b..d2dd96051649 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -31,7 +31,7 @@ /** * A Fluent DSL to create a hierarchy of filters for the CheckAndMutateRow RPCs and ReadRows Query. * - *

Intended usage is to statically import, or in case of conflict assign the static variable + *

Intended usage is to statically import, or in case of conflict, assign the static variable * FILTERS and use its fluent API to build filters. * *

Sample code: @@ -474,7 +474,7 @@ public RowFilter toProto() { case UNBOUNDED: break; default: - throw new IllegalStateException("Unknown start bound: " + getStartBound()); + throw new IllegalStateException("Unknown end bound: " + getStartBound()); } return RowFilter.newBuilder().setColumnRangeFilter(builder.build()).build(); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java index 2e76b42813ae..be28e155aeb0 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java @@ -30,8 +30,8 @@ public class Query { private final ReadRowsRequest.Builder builder = ReadRowsRequest.newBuilder(); /** - * Constructs a new Query object for the specified table id. The table id will be resolved against - * the instance id specified in the {@link + * Constructs a new Query object for the specified table id. The table id will be combined with + * the instance name specified in the {@link * com.google.cloud.bigtable.data.v2.BigtableDataSettings}. */ public static Query create(String tableId) { @@ -97,8 +97,11 @@ public Query range(ByteStringRange range) { case CLOSED: rangeBuilder.setStartKeyClosed(range.getStart()); break; + case UNBOUNDED: + rangeBuilder.clearStartKey(); + break; default: - throw new IllegalStateException("Unknown range bound: " + range.getStartBound()); + throw new IllegalStateException("Unknown start bound: " + range.getStartBound()); } switch (range.getEndBound()) { @@ -108,8 +111,11 @@ public Query range(ByteStringRange range) { case CLOSED: rangeBuilder.setEndKeyClosed(range.getEnd()); break; + case UNBOUNDED: + rangeBuilder.clearEndKey(); + break; default: - throw new IllegalStateException("Unknown range bound: " + range.getEndBound()); + throw new IllegalStateException("Unknown end bound: " + range.getEndBound()); } builder.getRowsBuilder().addRowRanges(rangeBuilder.build()); @@ -118,8 +124,8 @@ public Query range(ByteStringRange range) { } /** - * Sets the filter to look apply to each row. Only one filter can be set at a time. To use - * multiple filters, please use {@link Filters#interleave()} or {@link Filters#chain()}. + * Sets the filter to apply to each row. Only one filter can be set at a time. To use multiple + * filters, please use {@link Filters#interleave()} or {@link Filters#chain()}. */ public Query filter(Filters.Filter filter) { builder.setFilter(filter.toProto()); @@ -143,6 +149,7 @@ public ReadRowsRequest toProto(RequestContext requestContext) { requestContext.getInstanceName().getProject(), requestContext.getInstanceName().getInstance(), tableId); + return builder .setTableName(tableName.toString()) .setAppProfileId(requestContext.getAppProfileId()) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index 6f617077f2be..7f29ad1b538d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -17,11 +17,25 @@ import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; +import javax.annotation.Nonnull; /** * Range API. * - *

This base class represents the API for all ranges in the Cloud Bigtable client. + *

This base class represents the API for all ranges in the Cloud Bigtable client. It is an + * immutable class, so all of its modification methods return ne instances. It's intended to support + * fluent DSLs. For example: + * + *

{@code
+ * // A Range that encloses all strings
+ * ByteStringRange.unbounded();
+ *
+ * // Range that includes all strings including "begin" up until "end"
+ * ByteStringRange.unbounded().of("start", "end");
+ *
+ * // Create a Bytestring range with an unbounded start and the inclusive end "end"
+ * ByteStringRange.unbounded().endClosed("end");
+ * }
*/ abstract class Range> { public enum BoundType { @@ -40,37 +54,47 @@ public enum BoundType { } Range(BoundType startBound, T start, BoundType endBound, T end) { - this.start = start; this.startBound = startBound; - this.end = end; + this.start = start; this.endBound = endBound; + this.end = end; } /** * Creates a new {@link Range} with the specified inclusive start and the specified exclusive end. */ - public R of(T startClosed, T endOpen) { - return newInstance(BoundType.CLOSED, startClosed, BoundType.OPEN, endOpen); + public R of(@Nonnull T startClosed, @Nonnull T endOpen) { + return newInstanceSafe(BoundType.CLOSED, startClosed, BoundType.OPEN, endOpen); + } + + /** Creates a new {@link Range} with an unbounded start and the current end. */ + public R startUnbounded() { + return newInstanceSafe(BoundType.UNBOUNDED, null, endBound, end); } /** Creates a new {@link Range} with the specified exclusive start and the current end. */ - public R startOpen(T start) { - return newInstance(BoundType.OPEN, start, endBound, end); + public R startOpen(@Nonnull T start) { + return newInstanceSafe(BoundType.OPEN, start, endBound, end); } /** Creates a new {@link Range} with the specified inclusive start and the current end. */ - public R startClosed(T start) { - return newInstance(BoundType.CLOSED, start, endBound, end); + public R startClosed(@Nonnull T start) { + return newInstanceSafe(BoundType.CLOSED, start, endBound, end); + } + + /** Creates a new {@link Range} with the current start and an unbounded end. */ + public R endUnbounded() { + return newInstanceSafe(startBound, start, BoundType.UNBOUNDED, null); } /** Creates a new {@link Range} with the specified exclusive end and the current start. */ - public R endOpen(T end) { - return newInstance(startBound, start, BoundType.OPEN, end); + public R endOpen(@Nonnull T end) { + return newInstanceSafe(startBound, start, BoundType.OPEN, end); } /** Creates a new {@link Range} with the specified inclusive end and the current start. */ - public R endClosed(T end) { - return newInstance(startBound, start, BoundType.CLOSED, end); + public R endClosed(@Nonnull T end) { + return newInstanceSafe(startBound, start, BoundType.CLOSED, end); } /** Gets the current start {@link BoundType}. */ @@ -105,6 +129,15 @@ public T getEnd() { return end; } + private R newInstanceSafe(BoundType startBound, T start, BoundType endBound, T end) { + if (startBound != BoundType.UNBOUNDED) { + Preconditions.checkNotNull(start, "Bounded start can't be null."); + } + if (endBound != BoundType.UNBOUNDED) { + Preconditions.checkNotNull(end, "Bounded end can't be null"); + } + return newInstance(startBound, start, endBound, end); + } /** * Extension point for subclasses to override. This allows subclasses to maintain chainability. */ diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java index 55444831b18d..4956759a6f5e 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Row.java @@ -21,7 +21,6 @@ import com.google.protobuf.ByteString; import java.util.List; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** Default representation of a logical row. */ @BetaApi @@ -29,30 +28,30 @@ public abstract class Row implements Comparable { /** Creates a new instance of the {@link Row}. */ @InternalApi - public static Row create(ByteString key, List cells) { + public static Row create(ByteString key, List cells) { return new AutoValue_Row(key, cells); } /** Returns the row key */ @Nonnull - public abstract ByteString key(); + public abstract ByteString getKey(); /** * Returns the list of cells. The cells will be clustered by their family and sorted by their * qualifier. */ - public abstract List cells(); + public abstract List getCells(); /** Lexicographically compares this row's key to another row's key. */ @Override public int compareTo(@Nonnull Row row) { - int sizeA = key().size(); - int sizeB = row.key().size(); + int sizeA = getKey().size(); + int sizeB = row.getKey().size(); int size = Math.min(sizeA, sizeB); for (int i = 0; i < size; i++) { - int byteA = key().byteAt(i) & 0xff; - int byteB = row.key().byteAt(i) & 0xff; + int byteA = getKey().byteAt(i) & 0xff; + int byteB = row.getKey().byteAt(i) & 0xff; if (byteA == byteB) { continue; } else { @@ -64,42 +63,4 @@ public int compareTo(@Nonnull Row row) { } return sizeA < sizeB ? -1 : 1; } - - /** Default representation of a cell in a {@link Row}. */ - @AutoValue - public abstract static class Cell { - /** Creates a new instance of the {@link Cell}. */ - @InternalApi - public static Cell create( - String family, - ByteString qualifier, - long timestamp, - List labels, - ByteString value) { - return new AutoValue_Row_Cell(family, qualifier, timestamp, value, labels); - } - - /** The cell's family */ - @Nonnull - public abstract String family(); - - /** The cell's qualifier (column name) */ - @Nullable - public abstract ByteString qualifier(); - - /** The timestamp of the cell */ - public abstract long timestamp(); - - /** The value of the cell */ - @Nonnull - public abstract ByteString value(); - - /** - * The labels assigned to the cell - * - * @see Filters#label(String) - */ - @Nonnull - public abstract List labels(); - } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java index e7c94dd516e0..5c9692fcfa6a 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowAdapter.java @@ -19,9 +19,9 @@ import java.util.List; /** - * An extension point that allows end users to plugin a custom implementation of logical rows. This + * An extension point that allows end users to plug in a custom implementation of logical rows. This * useful in cases where the user would like to apply advanced client side filtering of cells. This - * adapter acts like a factory for SAX style a row builder. + * adapter acts like a factory for a SAX style row builder. */ public interface RowAdapter { /** Creates a new instance of a {@link RowBuilder}. */ diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowCell.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowCell.java new file mode 100644 index 000000000000..e90466d00f46 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/RowCell.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.wrappers; + +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.protobuf.ByteString; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Default representation of a cell in a {@link Row}. */ +@AutoValue +public abstract class RowCell { + /** Creates a new instance of the {@link RowCell}. */ + @InternalApi + public static RowCell create( + String family, ByteString qualifier, long timestamp, List labels, ByteString value) { + return new AutoValue_RowCell(family, qualifier, timestamp, value, labels); + } + + /** The cell's family */ + @Nonnull + public abstract String family(); + + /** The cell's qualifier (column name) */ + @Nullable + public abstract ByteString qualifier(); + + /** The timestamp of the cell */ + public abstract long timestamp(); + + /** The value of the cell */ + @Nonnull + public abstract ByteString value(); + + /** + * The labels assigned to the cell + * + * @see Filters#label(String) + */ + @Nonnull + public abstract List labels(); +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java index 212e7201a5f2..ac576dad7a71 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/DefaultRowAdapterTest.java @@ -17,7 +17,6 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; import com.google.cloud.bigtable.data.v2.wrappers.RowAdapter.RowBuilder; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -57,7 +56,7 @@ public void singleCellRowTest() { Row.create( ByteString.copyFromUtf8("my-key"), ImmutableList.of( - Cell.create( + RowCell.create( "my-family", ByteString.copyFromUtf8("my-qualifier"), 100, @@ -67,7 +66,7 @@ public void singleCellRowTest() { @Test public void multiCellTest() { - List expectedCells = Lists.newArrayList(); + List expectedCells = Lists.newArrayList(); rowBuilder.startRow(ByteString.copyFromUtf8("my-key")); @@ -79,7 +78,7 @@ public void multiCellTest() { rowBuilder.finishCell(); expectedCells.add( - Cell.create("family", qualifier, 1000, ImmutableList.of("my-label"), value)); + RowCell.create("family", qualifier, 1000, ImmutableList.of("my-label"), value)); } assertThat(rowBuilder.finishRow()) @@ -107,7 +106,7 @@ public void splitCellTest() { Row.create( ByteString.copyFromUtf8("my-key"), ImmutableList.of( - Cell.create( + RowCell.create( "family", ByteString.copyFromUtf8("qualifier"), 1000, diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java index 84cda0dbf316..ba0996fb0d47 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/wrappers/RowTest.java @@ -17,7 +17,6 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.cloud.bigtable.data.v2.wrappers.Row.Cell; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; import org.junit.Test; @@ -32,7 +31,7 @@ public void compareTest() { Row.create( ByteString.copyFromUtf8("key1"), ImmutableList.of( - Cell.create( + RowCell.create( "family", ByteString.EMPTY, 1000, @@ -42,7 +41,7 @@ public void compareTest() { Row.create( ByteString.copyFromUtf8("key2"), ImmutableList.of( - Cell.create( + RowCell.create( "family", ByteString.EMPTY, 1000, @@ -52,7 +51,7 @@ public void compareTest() { Row.create( ByteString.copyFromUtf8("key2"), ImmutableList.of( - Cell.create( + RowCell.create( "family2", ByteString.EMPTY, 1000, From bc0dae6d55d8aae1fd72fe3cf096e15bee515c2a Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Fri, 9 Feb 2018 10:44:48 -0500 Subject: [PATCH 13/18] move close() --- .../cloud/bigtable/data/v2/BigtableDataClient.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 2f17f146546e..39f2dd85ca2a 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 @@ -117,12 +117,6 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO this.stub = stub; } - /** Close the clients and releases all associated resources. */ - @Override - public void close() throws Exception { - stub.close(); - } - /** * Convenience method for synchronous streaming the results of a {@link Query}. * @@ -252,4 +246,10 @@ public ServerStreamingCallable readRowsCallable() { public ServerStreamingCallable readRowsCallable(RowAdapter rowAdapter) { return stub.createReadRowsCallable(rowAdapter); } + + /** Close the clients and releases all associated resources. */ + @Override + public void close() throws Exception { + stub.close(); + } } From 8be13399715767e439c5b1b24847d22653fb0f52 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Fri, 9 Feb 2018 10:50:01 -0500 Subject: [PATCH 14/18] sanity check query limit --- .../java/com/google/cloud/bigtable/data/v2/wrappers/Query.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java index be28e155aeb0..2e5586b6d436 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Query.java @@ -134,6 +134,7 @@ public Query filter(Filters.Filter filter) { /** Limits the number of rows that can be returned */ public Query limit(long limit) { + Preconditions.checkArgument(limit > 0, "Limit must be greater than 0."); builder.setRowsLimit(limit); return this; } From 1328f6a4ea58dcbac55a4c59f41e3810acfe67ce Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Fri, 9 Feb 2018 10:58:26 -0500 Subject: [PATCH 15/18] oops, forgot to commit this --- .../cloud/bigtable/data/v2/wrappers/Range.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index 7f29ad1b538d..9d0127cfe0ea 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -128,8 +128,8 @@ public T getEnd() { Preconditions.checkState(endBound != BoundType.UNBOUNDED, "End is unbounded"); return end; } - - private R newInstanceSafe(BoundType startBound, T start, BoundType endBound, T end) { + + R newInstanceSafe(BoundType startBound, T start, BoundType endBound, T end) { if (startBound != BoundType.UNBOUNDED) { Preconditions.checkNotNull(start, "Bounded start can't be null."); } @@ -175,27 +175,27 @@ abstract static class AbstractByteStringRange Date: Sat, 10 Feb 2018 00:46:31 -0500 Subject: [PATCH 16/18] fix error message --- .../com/google/cloud/bigtable/data/v2/wrappers/Filters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java index d2dd96051649..b279d6ae56cc 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Filters.java @@ -474,7 +474,7 @@ public RowFilter toProto() { case UNBOUNDED: break; default: - throw new IllegalStateException("Unknown end bound: " + getStartBound()); + throw new IllegalStateException("Unknown end bound: " + getEndBound()); } return RowFilter.newBuilder().setColumnRangeFilter(builder.build()).build(); From 6e64dc363c41c371be0b9d8c17968f08b3146e45 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 12 Feb 2018 10:54:43 -0500 Subject: [PATCH 17/18] address feedback --- .../bigtable/data/v2/internal/RegexUtil.java | 26 +++++++++++-------- .../bigtable/data/v2/wrappers/Range.java | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java index 0c9b147d6c38..c348ec740814 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RegexUtil.java @@ -18,10 +18,19 @@ import com.google.api.core.InternalApi; import com.google.protobuf.ByteString; import com.google.protobuf.ByteString.ByteIterator; -import com.google.protobuf.ByteString.Output; -import java.io.IOException; -import java.io.OutputStream; +/** + * Contains utilities to handle RE2 flavor regular expressions. This differs from {@link + * java.util.regex.Pattern} in two important ways: + * + *
    + *
  1. Binary strings are supported. + *
  2. The syntax is a lot more restricted but allows different modifiers. + *
+ * + *

See https://github.com/google/re2 for more + * details. + */ @InternalApi public final class RegexUtil { private static final byte[] NULL_BYTES = "\\x00".getBytes(); @@ -33,22 +42,17 @@ public static String literalRegex(final String value) { } /** Converts the value to a quoted regular expression. */ public static ByteString literalRegex(ByteString value) { - Output output = ByteString.newOutput(value.size() * 2); + ByteString.Output output = ByteString.newOutput(value.size() * 2); ByteIterator it = value.iterator(); - try { - writeLiteralRegex(it, output); - } catch (IOException e) { - throw new RuntimeException("Unexpected io error converting regex", e); - } + writeLiteralRegex(it, output); return output.toByteString(); } // Extracted from: re2 QuoteMeta: // https://github.com/google/re2/blob/70f66454c255080a54a8da806c52d1f618707f8a/re2/re2.cc#L456 - private static void writeLiteralRegex(ByteIterator input, OutputStream output) - throws IOException { + private static void writeLiteralRegex(ByteIterator input, ByteString.Output output) { while (input.hasNext()) { byte unquoted = input.nextByte(); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java index 9d0127cfe0ea..4062851f7616 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/wrappers/Range.java @@ -128,7 +128,7 @@ public T getEnd() { Preconditions.checkState(endBound != BoundType.UNBOUNDED, "End is unbounded"); return end; } - + R newInstanceSafe(BoundType startBound, T start, BoundType endBound, T end) { if (startBound != BoundType.UNBOUNDED) { Preconditions.checkNotNull(start, "Bounded start can't be null."); From 869da95c90266f14e9b702049b7777487f84122b Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 12 Feb 2018 11:05:01 -0500 Subject: [PATCH 18/18] move close to end --- .../bigtable/data/v2/stub/EnhancedBigtableStub.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index dc49fc4f9e4b..cae90fe50565 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -76,11 +76,6 @@ public static EnhancedBigtableStub create(EnhancedBigtableStubSettings settings) readRowsCallable = createReadRowsCallable(new DefaultRowAdapter()); } - @Override - public void close() throws Exception { - stub.close(); - } - // public ServerStreamingCallable createReadRowsCallable( RowAdapter rowAdapter) { @@ -99,4 +94,9 @@ public ServerStreamingCallable readRowsCallable() { return readRowsCallable; } // + + @Override + public void close() throws Exception { + stub.close(); + } }