Repository: GoogleCloudPlatform/cloud-bigtable-client
Pull request #2034 (2018-12-19 09:44:13)

Adding implementation for readRows
Added new operation `IBigtableDataClient#readRows(ReadRowsRequest)` which currently return `ResultScanner<Row>`, with intend to delegate `BigtableDataClient#readFlatRow` for vaneer client.

Reason for raising this PR:
- `FlatRow` and `v2.models.Row` has identical fields.
- Vanner client has all read operation with `v2.models.Row`.

**Assumption:** `RowCell` has same ordering as `FlatRow.Cell`. 

igorbernstein2: I think it would be better to keep the FlatRow abstraction in cloud-bigtable-client and instead pas a custom [RowAdapter](https://github.com/googleapis/google-cloud-java/blob/master/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/RowAdapter.java#L26) to [readRowsCallable](https://github.com/googleapis/google-cloud-java/blob/master/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java#L598).

This would keep the existing api stable w/o any performance loss
igorbernstein2: See here for an example implementation:
https://github.com/googleapis/google-cloud-java/blob/master/google-cloud-clients/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/DefaultRowAdapter.java
rahulKQL: Thanks @igorbernstein2 for review. I have added `FlatRowModelAdapter.java` which we could use for `BigtableDataClient#readRowsCallable` later when BigtableDataClient vaneer client is ready.

When get a chance please have a look!! 

Patch:
diff --git a/README.md b/README.md
index b710d671c..0a44aa4a9 100644
--- a/README.md
+++ b/README.md
@@ -44,21 +44,23 @@ which makes it easy for development teams to get started.
   <dependency>
     <groupId>com.google.cloud.bigtable</groupId>
     <artifactId>bigtable-hbase-1.x</artifactId>
-    <version>1.6.0</version>
+    <version>1.8.0</version>
   </dependency>
   ```
 
   Gradle:
   ```Groovy
-  compile 'com.google.cloud.bigtable:bigtable-hbase-1.x:1.6.0'
+  compile 'com.google.cloud.bigtable:bigtable-hbase-1.x:1.8.0'
   ```
 
   SBT:
   ```Scala
-  libraryDependencies += "com.google.cloud.bigtable" % "bigtable-hbase-1.x" % "1.6.0"
+  libraryDependencies += "com.google.cloud.bigtable" % "bigtable-hbase-1.x" % "1.8.0"
   ```
 
-* Refer to the [Java samples documentation](https://cloud.google.com/bigtable/docs/samples) for detailed demonstrations of how to read and write data with Cloud Bigtable. The code for these samples is available in the [Cloud Bigtable examples project](https://github.com/GoogleCloudPlatform/cloud-bigtable-examples).
+* Refer to the [Java samples documentation](https://cloud.google.com/bigtable/docs/samples) for detailed demonstrations of how to read and write data with Cloud Bigtable. The code for these samples is avail
+
+able in the [Cloud Bigtable examples project](https://github.com/GoogleCloudPlatform/cloud-bigtable-examples).
 
 ## Questions and discussions
 
@@ -122,7 +124,7 @@ maven repository. When you disable that profile, attach workspace sources to the
 
 | Name | Language | Repository | latest version | status |
 | --- | --- | --- | --- | --- |
-| Cloud Bigtable Examples | Java, others | [GoogleCloudPlatform/cloud-bitgable-examples][maven-examples-repo] | | |
+| Cloud Bigtable Examples | Java, others | [GoogleCloudPlatform/cloud-bigtable-examples][maven-examples-repo] | | |
 | HBase client | Java | [GoogleCloudPlatform/cloud-bigtable-client][maven-hbase-client-repo] | [![Maven][maven-hbase-shield]][maven-hbase-client-maven-search] | GA |
 | Cloud Bigtable GoLang | Go | [googleapis/google-cloud-go](https://github.com/googleapis/google-cloud-go) | N/A | GA |
 | Cloud Bigtable Java | java | [googleapis/google-cloud-java](http://github.com/googleapis/google-cloud-java) | [![Maven][maven-google-cloud-java-shield]][maven-google-cloud-java-maven-search] | Beta |
diff --git a/bigtable-client-core-parent/bigtable-client-core/pom.xml b/bigtable-client-core-parent/bigtable-client-core/pom.xml
index add717c9e..0664547de 100644
--- a/bigtable-client-core-parent/bigtable-client-core/pom.xml
+++ b/bigtable-client-core-parent/bigtable-client-core/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-core-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-client-core</artifactId>
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/core/IBigtableDataClient.java b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/core/IBigtableDataClient.java
index d952330ca..0866ca7ee 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/core/IBigtableDataClient.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/core/IBigtableDataClient.java
@@ -15,12 +15,11 @@
  */
 package com.google.cloud.bigtable.core;
 
-import com.google.api.core.ApiFuture;
 import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
 import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
 import com.google.cloud.bigtable.data.v2.models.Row;
 import com.google.cloud.bigtable.data.v2.models.RowMutation;
-
+import com.google.common.util.concurrent.ListenableFuture;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -41,17 +40,18 @@ public interface IBigtableDataClient {
    * Mutate a row atomically.
    *
    * @param rowMutation a {@link RowMutation} model object.
-   * @return a {@link ApiFuture} of type {@link Void} will be set when request is
+   * @return a {@link ListenableFuture} of type {@link Void} will be set when request is
    *     successful otherwise exception will be thrown.
    * @throws InterruptedException if any.
    */
-  ApiFuture<Void> mutateRowAsync(RowMutation rowMutation) throws InterruptedException;
+  //TODO(rahulkql): Once it is adapted to v2.models, change the return type to ApiFuture.
+  ListenableFuture<Void> mutateRowAsync(RowMutation rowMutation) throws InterruptedException;
 
   /**
    * Perform an atomic read-modify-write operation on a row.
    *
    * @param readModifyWriteRow a {@link ReadModifyWriteRow} model object.
-   * @return Row a modified row.
+   * @return {@link Row} a modified row.
    * @throws ExecutionException if any.
    * @throws InterruptedException if any.
    */
@@ -62,11 +62,12 @@ public interface IBigtableDataClient {
    * Perform an atomic read-modify-write operation on a row.
    *
    * @param readModifyWriteRow a {@link ReadModifyWriteRow} model object.
-   * @return a {@link ApiFuture} of type {@link Row} will be set when request is
+   * @return a {@link ListenableFuture} of type {@link Row} will be set when request is
    *     successful otherwise exception will be thrown.
    * @throws InterruptedException if any.
    */
-  ApiFuture<Row> readModifyWriteRowAsync(ReadModifyWriteRow readModifyWriteRow) throws InterruptedException;
+  //TODO(rahulkql): Once it is adapted to v2.models, change the return type to ApiFuture.
+  ListenableFuture<Row> readModifyWriteRowAsync(ReadModifyWriteRow readModifyWriteRow) throws InterruptedException;
 
   /**
    * Creates BulMutation batcher.
@@ -77,16 +78,17 @@ public interface IBigtableDataClient {
    * Mutate a row atomically dependent on a precondition.
    *
    * @param conditionalRowMutation a {@link ConditionalRowMutation} model object.
-   * @return a {@link ApiFuture} of type {@link Boolean} will be set when request is
+   * @return a {@link ListenableFuture} of type {@link Boolean} will be set when request is
    *     successful otherwise exception will be thrown.
    */
-  ApiFuture<Boolean> checkAndMutateRowAsync(ConditionalRowMutation conditionalRowMutation);
+  //TODO(rahulkql): Once it is adapted to v2.models, change the return type to ApiFuture.
+  ListenableFuture<Boolean> checkAndMutateRowAsync(ConditionalRowMutation conditionalRowMutation);
 
   /**
    * Mutate a row atomically dependent on a precondition.
    *
    * @param conditionalRowMutation a {@link ConditionalRowMutation} model object.
-   * @return Boolean returns true if predicate returns any result.
+   * @return returns true if predicate returns any result.
    * @throws ExecutionException if any.
    * @throws InterruptedException if any.
    */
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/BigtableDataClientWrapper.java b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/BigtableDataClientWrapper.java
index ddf1314ef..bab3a03d3 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/BigtableDataClientWrapper.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/BigtableDataClientWrapper.java
@@ -15,8 +15,14 @@
  */
 package com.google.cloud.bigtable.grpc;
 
-import com.google.api.core.ApiFuture;
+import com.google.bigtable.v2.Cell;
+import com.google.bigtable.v2.CheckAndMutateRowRequest;
+import com.google.bigtable.v2.CheckAndMutateRowResponse;
+import com.google.bigtable.v2.Column;
+import com.google.bigtable.v2.Family;
 import com.google.bigtable.v2.MutateRowRequest;
+import com.google.bigtable.v2.MutateRowResponse;
+import com.google.bigtable.v2.ReadModifyWriteRowResponse;
 import com.google.cloud.bigtable.config.BigtableOptions;
 import com.google.cloud.bigtable.core.IBigtableDataClient;
 import com.google.cloud.bigtable.core.IBulkMutation;
@@ -25,11 +31,18 @@ import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
 import com.google.cloud.bigtable.data.v2.models.InstanceName;
 import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
 import com.google.cloud.bigtable.data.v2.models.Row;
+import com.google.cloud.bigtable.data.v2.models.RowCell;
 import com.google.cloud.bigtable.data.v2.models.RowMutation;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import javax.annotation.Nullable;
 
 /**
  * This class implements the {@link IBigtableDataClient} interface which provides access to google cloud
- * java
+ * java.
  */
 public class BigtableDataClientWrapper implements IBigtableDataClient {
 
@@ -46,39 +59,100 @@ public class BigtableDataClientWrapper implements IBigtableDataClient {
         );
   }
 
+  /** {@inheritDoc} */
   @Override
   public void mutateRow(RowMutation rowMutation) {
     MutateRowRequest mutateRowRequest = rowMutation.toProto(requestContext);
     delegate.mutateRow(mutateRowRequest);
   }
 
+  /** {@inheritDoc} */
   @Override
-  public ApiFuture<Void> mutateRowAsync(RowMutation rowMutation) {
-    throw new UnsupportedOperationException("Not implemented yet");
+  public ListenableFuture<Void> mutateRowAsync(RowMutation rowMutation) {
+    ListenableFuture<MutateRowResponse> response =
+        delegate.mutateRowAsync(rowMutation.toProto(requestContext));
+    return  Futures.transform(response, new Function<MutateRowResponse, Void>() {
+      @Nullable
+      @Override
+      public Void apply(@Nullable MutateRowResponse mutateRowResponse) {
+        return null;
+      }
+    }, MoreExecutors.directExecutor());
   }
 
+  /** {@inheritDoc} */
   @Override
   public Row readModifyWriteRow(ReadModifyWriteRow readModifyWriteRow) {
-    throw new UnsupportedOperationException("Not implemented yet");
+    ReadModifyWriteRowResponse response =
+        delegate.readModifyWriteRow(readModifyWriteRow.toProto(requestContext));
+    return transformResponse(response);
   }
 
+  /** {@inheritDoc} */
   @Override
-  public ApiFuture<Row> readModifyWriteRowAsync(ReadModifyWriteRow readModifyWriteRow) {
-    throw new UnsupportedOperationException("Not implemented yet");
+  public ListenableFuture<Row> readModifyWriteRowAsync(ReadModifyWriteRow readModifyWriteRow) {
+    ListenableFuture<ReadModifyWriteRowResponse> response =
+        delegate.readModifyWriteRowAsync(readModifyWriteRow.toProto(requestContext));
+    return Futures.transform(response, new Function<ReadModifyWriteRowResponse, Row>() {
+      @Override
+      public Row apply(ReadModifyWriteRowResponse readModifyRowResponse) {
+        return transformResponse(readModifyRowResponse);
+      }
+    }, MoreExecutors.directExecutor());
   }
 
+  /** {@inheritDoc} */
   @Override
   public IBulkMutation createBulkMutationBatcher() {
     throw new UnsupportedOperationException("Not implemented yet");
   }
 
+  /** {@inheritDoc} */
   @Override
-  public ApiFuture<Boolean> checkAndMutateRowAsync(ConditionalRowMutation conditionalRowMutation) {
-    throw new UnsupportedOperationException("Not implemented yet");
+  public ListenableFuture<Boolean> checkAndMutateRowAsync(
+      ConditionalRowMutation conditionalRowMutation) {
+    final CheckAndMutateRowRequest request = conditionalRowMutation.toProto(requestContext);
+    final ListenableFuture<CheckAndMutateRowResponse> response =
+        delegate.checkAndMutateRowAsync(request);
+    return Futures.transform(response, new Function<CheckAndMutateRowResponse, Boolean>() {
+
+      @Override
+      public Boolean apply(CheckAndMutateRowResponse checkAndMutateRowResponse) {
+        return checkAndMutateRowResponse.getPredicateMatched();
+      }
+    }, MoreExecutors.directExecutor());
   }
 
+  /** {@inheritDoc} */
   @Override
   public Boolean checkAndMutateRow(ConditionalRowMutation conditionalRowMutation) {
-    throw new UnsupportedOperationException("Not implemented yet");
+    CheckAndMutateRowResponse response =
+        delegate.checkAndMutateRow(conditionalRowMutation.toProto(requestContext));
+    return response.getPredicateMatched();
+  }
+
+  /**
+   * This method converts instances of {@link ReadModifyWriteRowResponse} to {@link Row}.
+   *
+   * @param response an instance of {@link ReadModifyWriteRowResponse} type.
+   * @return an instance of {@link Row}.
+   */
+  private Row transformResponse(ReadModifyWriteRowResponse response) {
+    ImmutableList.Builder<RowCell> rowCells  = ImmutableList.builder();
+
+    for (Family family : response.getRow().getFamiliesList()) {
+      for (Column column : family.getColumnsList()) {
+        for (Cell cell : column.getCellsList()) {
+          rowCells.add(
+              RowCell.create(
+                  family.getName(),
+                  column.getQualifier(),
+                  cell.getTimestampMicros(),
+                  cell.getLabelsList(),
+                  cell.getValue()));
+        }
+      }
+    }
+    return Row.create(response.getRow().getKey(), rowCells.build());
   }
 }
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/scanner/FlatRowAdapter.java b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/scanner/FlatRowAdapter.java
new file mode 100644
index 000000000..6fa6b8dab
--- /dev/null
+++ b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/scanner/FlatRowAdapter.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.grpc.scanner;
+
+import com.google.cloud.bigtable.data.v2.models.RowAdapter;
+import com.google.cloud.bigtable.grpc.scanner.FlatRow.Cell;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+/**
+ * Adapter for {@link RowAdapter} that uses {@link FlatRow}'s to represent logical rows.
+ */
+public class FlatRowAdapter implements RowAdapter<FlatRow> {
+
+  /** {@inheritDoc} */
+  @Override
+  public RowBuilder<FlatRow> createRowBuilder() {
+    return new FlatRowAdapter.FlatRowBuilder();
+  }
+
+  public class FlatRowBuilder implements RowBuilder<FlatRow> {
+    private ByteString currentKey;
+    private String family;
+    private ByteString qualifier;
+    private List<String> labels;
+    private long timestamp;
+    private ByteString value;
+
+    /*
+     * cells contains list of {@link Cell} for all the families.
+     */
+    private Map<String, List<Cell>> cells = new TreeMap<>();
+
+    /*
+     * currentFamilyCells is buffered with current family's {@link Cell}s.
+     */
+    private List<Cell> currentFamilyCells = null;
+    private String previousFamily;
+    private int totalCellCount = 0;
+
+    public FlatRowBuilder() {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void startRow(ByteString rowKey) {
+      this.currentKey = rowKey;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void startCell(String family, ByteString qualifier, long timestamp, List<String> 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);
+    }
+
+    /**
+     * Adds a Cell to {@link Cell}'s map which is ordered by family. cells received from
+     * {@link RowBuilder} has ordering as:
+     *   <ul>
+     *     <li>family names clustered, but not sorted</li>
+     *     <li>qualifiers in each family cluster is sorted lexicographically</li>
+     *     <li>then descending by timestamp</li>
+     *   </ul>
+     * The end result will be that {@link Cell} are ordered as:
+     *    <ul>
+     *      <li>lexicographical by family</li>
+     *      <li>then lexicographical by qualifier</li>
+     *      <li>then descending by timestamp</li>
+     *    </ul>
+     * A flattened version of the {@link Cell} map will be sorted correctly.
+     */
+    @Override
+    public void finishCell() {
+      if (!Objects.equals(this.family, this.previousFamily)) {
+        previousFamily = this.family;
+        currentFamilyCells = new ArrayList<>();
+        cells.put(this.family, this.currentFamilyCells);
+      }
+
+      FlatRow.Cell cell  = new FlatRow.Cell(this.family, this.qualifier, this.timestamp,
+                    this.value, this.labels);
+      this.currentFamilyCells.add(cell);
+      totalCellCount++;
+  }
+
+    /**
+     * This method flattens the {@link #cells} which has a map of Lists keyed by family name.
+     * The {@link #cells} TreeMap is sorted lexicographically, and each List is sorted by
+     * qualifier in lexicographically ascending order, and timestamp in descending order.
+     *
+     * @return an object of HBase {@link FlatRow}.
+     */
+    @Override
+    public FlatRow finishRow() {
+      ImmutableList.Builder<FlatRow.Cell> combined = ImmutableList.builderWithExpectedSize(totalCellCount);
+      for (List<FlatRow.Cell> familyCellList : cells.values()) {
+        combined.addAll(familyCellList);
+      }
+
+      return new FlatRow(this.currentKey, combined.build());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void reset() {
+      this.currentKey = null;
+      this.family = null;
+      this.qualifier = null;
+      this.labels = null;
+      this.timestamp = 0L;
+      this.value = null;
+      this.cells = new TreeMap<>();
+      this.currentFamilyCells = null;
+      this.previousFamily = null;
+      this.totalCellCount = 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FlatRow createScanMarkerRow(ByteString rowKey) {
+      return new FlatRow(rowKey, ImmutableList.<Cell>of());
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isScanMarkerRow(FlatRow row) {
+    return row.getCells().isEmpty();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public ByteString getKey(FlatRow row) {
+    return row.getRowKey();
+  }
+}
diff --git a/bigtable-client-core-parent/bigtable-hbase/pom.xml b/bigtable-client-core-parent/bigtable-hbase/pom.xml
index b16c87d96..7cee5610d 100644
--- a/bigtable-client-core-parent/bigtable-hbase/pom.xml
+++ b/bigtable-client-core-parent/bigtable-hbase/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-core-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase</artifactId>
diff --git a/bigtable-client-core-parent/pom.xml b/bigtable-client-core-parent/pom.xml
index 7d428377d..30c16c4e5 100644
--- a/bigtable-client-core-parent/pom.xml
+++ b/bigtable-client-core-parent/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-client-core-parent</artifactId>
diff --git a/bigtable-dataflow-parent/bigtable-beam-import/pom.xml b/bigtable-dataflow-parent/bigtable-beam-import/pom.xml
index 9cdc4c244..1c163bc34 100644
--- a/bigtable-dataflow-parent/bigtable-beam-import/pom.xml
+++ b/bigtable-dataflow-parent/bigtable-beam-import/pom.xml
@@ -18,7 +18,7 @@ limitations under the License.
   <parent>
     <groupId>com.google.cloud.bigtable</groupId>
     <artifactId>bigtable-dataflow-parent</artifactId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/bigtable-dataflow-parent/bigtable-beam-import/src/main/java/com/google/cloud/bigtable/beam/sequencefiles/ExportJob.java b/bigtable-dataflow-parent/bigtable-beam-import/src/main/java/com/google/cloud/bigtable/beam/sequencefiles/ExportJob.java
index 8463767e0..4e655566a 100644
--- a/bigtable-dataflow-parent/bigtable-beam-import/src/main/java/com/google/cloud/bigtable/beam/sequencefiles/ExportJob.java
+++ b/bigtable-dataflow-parent/bigtable-beam-import/src/main/java/com/google/cloud/bigtable/beam/sequencefiles/ExportJob.java
@@ -62,7 +62,7 @@ import org.apache.hadoop.io.serializer.WritableSerialization;
  *    --zone=[ZONE] \
  *    --bigtableInstanceId=[INSTANCE] \
  *    --bigtableTableId=[TABLE] \
- *    --destination=gs://[BUCLET]/[EXPORT_PATH] \
+ *    --destination=gs://[BUCKET]/[EXPORT_PATH] \
  *    --maxNumWorkers=[nodes * 10]"
  * }
  * </pre>
diff --git a/bigtable-dataflow-parent/bigtable-hbase-beam/pom.xml b/bigtable-dataflow-parent/bigtable-hbase-beam/pom.xml
index 8955e3a3e..c8055e6b2 100755
--- a/bigtable-dataflow-parent/bigtable-hbase-beam/pom.xml
+++ b/bigtable-dataflow-parent/bigtable-hbase-beam/pom.xml
@@ -16,7 +16,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-dataflow-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-beam</artifactId>
diff --git a/bigtable-dataflow-parent/pom.xml b/bigtable-dataflow-parent/pom.xml
index 79ac7d792..4055378a3 100644
--- a/bigtable-dataflow-parent/pom.xml
+++ b/bigtable-dataflow-parent/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-dataflow-parent</artifactId>
diff --git a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-hadoop/pom.xml b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-hadoop/pom.xml
index 5513a708f..22edaa9e4 100644
--- a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-hadoop/pom.xml
+++ b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-hadoop/pom.xml
@@ -18,7 +18,7 @@ limitations under the License.
   <parent>
     <artifactId>bigtable-hbase-1.x-parent</artifactId>
     <groupId>com.google.cloud.bigtable</groupId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-mapreduce/pom.xml b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-mapreduce/pom.xml
index d9b2b1f35..8c509eb4a 100644
--- a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-mapreduce/pom.xml
+++ b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-mapreduce/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-hbase-1.x-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-1.x-mapreduce</artifactId>
diff --git a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-shaded/pom.xml b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-shaded/pom.xml
index 3d7043e22..6aeb2a7da 100644
--- a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-shaded/pom.xml
+++ b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-shaded/pom.xml
@@ -18,7 +18,7 @@ limitations under the License.
   <parent>
     <artifactId>bigtable-hbase-1.x-parent</artifactId>
     <groupId>com.google.cloud.bigtable</groupId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x/pom.xml b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x/pom.xml
index 3b00d863b..5a03d4ca8 100644
--- a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x/pom.xml
+++ b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
   <parent>
     <groupId>com.google.cloud.bigtable</groupId>
     <artifactId>bigtable-hbase-1.x-parent</artifactId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>bigtable-hbase-1.x</artifactId>
diff --git a/bigtable-hbase-1.x-parent/pom.xml b/bigtable-hbase-1.x-parent/pom.xml
index f98563175..cff4696d9 100644
--- a/bigtable-hbase-1.x-parent/pom.xml
+++ b/bigtable-hbase-1.x-parent/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-1.x-parent</artifactId>
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-hadoop/pom.xml b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-hadoop/pom.xml
index 6d40f2a03..730b55bf5 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-hadoop/pom.xml
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-hadoop/pom.xml
@@ -18,7 +18,7 @@ limitations under the License.
   <parent>
     <artifactId>bigtable-hbase-2.x-parent</artifactId>
     <groupId>com.google.cloud.bigtable</groupId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
index 26901f072..f11b1edaf 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
@@ -18,7 +18,7 @@ limitations under the License.
   <parent>
     <artifactId>bigtable-hbase-2.x-parent</artifactId>
     <groupId>com.google.cloud.bigtable</groupId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/pom.xml b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/pom.xml
index 5b1e6d690..4007bdfb9 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/pom.xml
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
   <parent>
     <groupId>com.google.cloud.bigtable</groupId>
     <artifactId>bigtable-hbase-2.x-parent</artifactId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
   </parent>
 
   <properties>
diff --git a/bigtable-hbase-2.x-parent/pom.xml b/bigtable-hbase-2.x-parent/pom.xml
index 1611907e1..e8892fea2 100644
--- a/bigtable-hbase-2.x-parent/pom.xml
+++ b/bigtable-hbase-2.x-parent/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-2.x-parent</artifactId>
diff --git a/pom.xml b/pom.xml
index d6d4adc8b..d1665324b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@ limitations under the License.
 
     <groupId>com.google.cloud.bigtable</groupId>
     <artifactId>bigtable-client-parent</artifactId>
-    <version>1.7.1-SNAPSHOT</version>
+    <version>1.8.1-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>${project.groupId}:${project.artifactId}</name>
     <url>https://cloud.google.com/bigtable/</url>


Test patch:
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/TestBigtableDataClientWrapper.java b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/TestBigtableDataClientWrapper.java
index f6690efc2..9e5aea397 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/TestBigtableDataClientWrapper.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/TestBigtableDataClientWrapper.java
@@ -15,19 +15,40 @@
  */
 package com.google.cloud.bigtable.grpc;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.bigtable.v2.Cell;
+import com.google.bigtable.v2.CheckAndMutateRowRequest;
+import com.google.bigtable.v2.CheckAndMutateRowResponse;
+import com.google.bigtable.v2.Column;
+import com.google.bigtable.v2.Family;
 import com.google.bigtable.v2.MutateRowRequest;
 import com.google.bigtable.v2.MutateRowResponse;
+import com.google.bigtable.v2.ReadModifyWriteRowRequest;
+import com.google.bigtable.v2.ReadModifyWriteRowResponse;
+import com.google.bigtable.v2.Row;
 import com.google.cloud.bigtable.config.BigtableOptions;
 import com.google.cloud.bigtable.data.v2.internal.RequestContext;
+import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
 import com.google.cloud.bigtable.data.v2.models.InstanceName;
+import com.google.cloud.bigtable.data.v2.models.Mutation;
+import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
+import com.google.cloud.bigtable.data.v2.models.RowCell;
 import com.google.cloud.bigtable.data.v2.models.RowMutation;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.protobuf.ByteString;
+import java.util.Collections;
+import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -37,27 +58,225 @@ public class TestBigtableDataClientWrapper {
   private static final String INSTANCE_ID = "instanceId";
   private static final String TABLE_ID = "tableId";
   private static final String APP_PROFILE_ID = "appProfileId";
+  private static final ByteString QUALIFIER_1 = ByteString.copyFromUtf8("qualifier1");
+  private static final ByteString QUALIFIER_2 = ByteString.copyFromUtf8("qualifier2");
+  private static final int TIMESTAMP = 12345;
+  private static final String LABEL = "label";
+  private static final List<String> LABEL_LIST = Collections.singletonList(LABEL);
+  private static final ByteString VALUE = ByteString.copyFromUtf8("test-value");
+  private static final ByteString ROW_KEY = ByteString.copyFromUtf8("test-key");
+
   private static final RequestContext REQUEST_CONTEXT =
       RequestContext.create(InstanceName.of(PROJECT_ID, INSTANCE_ID), APP_PROFILE_ID);
-  private BigtableDataClient bigtableDataClient;
-  private BigtableOptions options;
-  private BigtableDataClientWrapper bigtableDataClientWrapper;
+
+  private BigtableOptions options =
+      BigtableOptions.builder().setProjectId(PROJECT_ID).setInstanceId(INSTANCE_ID)
+        .setAppProfileId(APP_PROFILE_ID).build();
+  @Mock
+  private BigtableDataClient client;
+
+  private BigtableDataClientWrapper clientWrapper;
 
   @Before
   public void setUp() {
-    bigtableDataClient = Mockito.mock(BigtableDataClient.class);
-    options = BigtableOptions.builder().setProjectId(PROJECT_ID).setInstanceId(INSTANCE_ID)
-        .setAppProfileId(APP_PROFILE_ID).build();
-    bigtableDataClientWrapper = new BigtableDataClientWrapper(bigtableDataClient, options);
+    clientWrapper = new BigtableDataClientWrapper(client, options);
   }
 
   @Test
   public void testMutateRow() {
     RowMutation rowMutation = RowMutation.create(TABLE_ID, "key");
     MutateRowRequest mutateRowRequest = rowMutation.toProto(REQUEST_CONTEXT);
-    when(bigtableDataClient.mutateRow(mutateRowRequest))
-        .thenReturn(MutateRowResponse.getDefaultInstance());
-    bigtableDataClientWrapper.mutateRow(rowMutation);
-    verify(bigtableDataClient).mutateRow(mutateRowRequest);
+    when(client.mutateRow(mutateRowRequest)).thenReturn(MutateRowResponse.getDefaultInstance());
+    clientWrapper.mutateRow(rowMutation);
+    verify(client).mutateRow(mutateRowRequest);
+  }
+
+  @Test
+  public void testMutateRowAsync() {
+    RowMutation rowMutation = RowMutation.create(TABLE_ID, "key");
+    MutateRowRequest request = rowMutation.toProto(REQUEST_CONTEXT);
+    ListenableFuture<MutateRowResponse> response =
+        Futures.immediateFuture(MutateRowResponse.getDefaultInstance());
+    when(client.mutateRowAsync(request)).thenReturn(response);
+    clientWrapper.mutateRowAsync(rowMutation);
+    verify(client).mutateRowAsync(request);
+  }
+
+  @Test
+  public void testCheckMutateRow(){
+    Mutation mutation = Mutation.create();
+    mutation.setCell("family", "qualifier", "some other value");
+    ConditionalRowMutation conditonalMutation =
+        ConditionalRowMutation.create(TABLE_ID, "first" + "-row" + "-key").then(mutation);
+    CheckAndMutateRowRequest request = conditonalMutation.toProto(REQUEST_CONTEXT);
+    CheckAndMutateRowResponse response =
+        CheckAndMutateRowResponse.newBuilder().setPredicateMatched(true).build();
+    when(client.checkAndMutateRow(request)).thenReturn(response);
+    Boolean actual = clientWrapper.checkAndMutateRow(conditonalMutation);
+    verify(client).checkAndMutateRow(request);
+    assertTrue(actual);
+  }
+
+  @Test
+  public void testCheckMutateRowWhenNoPredicateMatch(){
+    Mutation mutation = Mutation.create();
+    mutation.setCell("family", "qualifier", "some other value");
+    ConditionalRowMutation conditonalMutation =
+        ConditionalRowMutation.create(TABLE_ID, "first" + "-row" + "-key").then(mutation);
+    CheckAndMutateRowRequest request = conditonalMutation.toProto(REQUEST_CONTEXT);
+    CheckAndMutateRowResponse response =
+        CheckAndMutateRowResponse.newBuilder().setPredicateMatched(false).build();
+    when(client.checkAndMutateRow(request)).thenReturn(response);
+    Boolean actual = clientWrapper.checkAndMutateRow(conditonalMutation);
+    verify(client).checkAndMutateRow(request);
+    assertFalse(actual);
+  }
+
+  @Test
+  public void testCheckMutateRowAsync() throws Exception{
+    Mutation mutation = Mutation.create();
+    mutation.setCell("family", "qualifier", "some other value");
+    ConditionalRowMutation conditonalMutation =
+        ConditionalRowMutation.create(TABLE_ID, "first" + "-row" + "-key").then(mutation);
+    CheckAndMutateRowRequest request = conditonalMutation.toProto(REQUEST_CONTEXT);
+    CheckAndMutateRowResponse response =
+        CheckAndMutateRowResponse.newBuilder().setPredicateMatched(true).build();
+    ListenableFuture<CheckAndMutateRowResponse> future = Futures.immediateFuture(response);
+
+    when(client.checkAndMutateRowAsync(request)).thenReturn(future);
+    ListenableFuture<Boolean> actual = clientWrapper.checkAndMutateRowAsync(conditonalMutation);
+    verify(client).checkAndMutateRowAsync(request);
+    assertTrue(actual.get());
+  }
+
+  @Test
+  public void testCheckMutateRowAsyncWhenNoPredicateMatch() throws Exception{
+    Mutation mutation = Mutation.create();
+    mutation.setCell("family", "qualifier", "some other value");
+    ConditionalRowMutation conditonalMutation =
+        ConditionalRowMutation.create(TABLE_ID, "first" + "-row" + "-key").then(mutation);
+    CheckAndMutateRowRequest request = conditonalMutation.toProto(REQUEST_CONTEXT);
+    CheckAndMutateRowResponse response =
+        CheckAndMutateRowResponse.newBuilder().setPredicateMatched(false).build();
+    ListenableFuture<CheckAndMutateRowResponse> future = Futures.immediateFuture(response);
+
+    when(client.checkAndMutateRowAsync(request)).thenReturn(future);
+    ListenableFuture<Boolean> actual = clientWrapper.checkAndMutateRowAsync(conditonalMutation);
+    verify(client).checkAndMutateRowAsync(request);
+    assertFalse(actual.get());
+  }
+
+  @Test
+  public void testReadModifyWriteWithOneCell(){
+    ReadModifyWriteRow readModify = ReadModifyWriteRow.create(TABLE_ID, "test-key");
+    ReadModifyWriteRowRequest request = readModify.toProto(REQUEST_CONTEXT);
+    Row expectedRow = buildRow();
+    ReadModifyWriteRowResponse response =
+        ReadModifyWriteRowResponse.newBuilder().setRow(expectedRow).build();
+    RowCell rowCell = RowCell.create("firstFamily", QUALIFIER_1, TIMESTAMP, LABEL_LIST, VALUE);
+    com.google.cloud.bigtable.data.v2.models.Row modelRow =
+        com.google.cloud.bigtable.data.v2.models.Row.create(ROW_KEY, Collections.singletonList(rowCell));
+
+    when(client.readModifyWriteRow(request)).thenReturn(response);
+    com.google.cloud.bigtable.data.v2.models.Row actualRow =
+        clientWrapper.readModifyWriteRow(readModify);
+    assertEquals(modelRow, actualRow);
+    verify(client).readModifyWriteRow(request);
+  }
+
+  @Test
+  public void testReadModifyWriteAsync() throws Exception{
+    ReadModifyWriteRow readModify = ReadModifyWriteRow.create(TABLE_ID, "test-key");
+    ReadModifyWriteRowRequest request = readModify.toProto(REQUEST_CONTEXT);
+    Row expectedRow = buildRow();
+    ReadModifyWriteRowResponse response =
+        ReadModifyWriteRowResponse.newBuilder().setRow(expectedRow).build();
+    ListenableFuture<ReadModifyWriteRowResponse> listenableResponse =
+        Futures.immediateFuture(response);
+    RowCell rowCell = RowCell.create("firstFamily", QUALIFIER_1, TIMESTAMP, LABEL_LIST, VALUE);
+    com.google.cloud.bigtable.data.v2.models.Row modelRow =
+        com.google.cloud.bigtable.data.v2.models.Row.create(ROW_KEY, Collections.singletonList(rowCell));
+
+    when(client.readModifyWriteRowAsync(request)).thenReturn(listenableResponse);
+    ListenableFuture<com.google.cloud.bigtable.data.v2.models.Row> output =
+        clientWrapper.readModifyWriteRowAsync(readModify);
+    assertEquals(modelRow, output.get());
+    verify(client).readModifyWriteRowAsync(request);
+  }
+
+  private static Row buildRow() {
+    Cell cell = Cell.newBuilder()
+        .setValue(VALUE)
+        .setTimestampMicros(TIMESTAMP)
+        .addLabels(LABEL)
+        .build();
+    return Row.newBuilder()
+        .setKey(ROW_KEY)
+        .addFamilies(Family.newBuilder()
+            .setName("firstFamily")
+            .addColumns(Column.newBuilder()
+                .setQualifier(QUALIFIER_1)
+                .addCells(cell)
+                .build())
+            .build())
+        .build();
+  }
+
+  @Test
+  public void testReadModifyWriteWithMultipleCell(){
+    Row row = Row.newBuilder()
+        .setKey(ROW_KEY)
+        .addFamilies(Family.newBuilder()
+            .setName("firstFamily")
+            .addColumns(Column.newBuilder()
+                .setQualifier(QUALIFIER_1)
+                .addCells(Cell.newBuilder()
+                    .setValue(VALUE)
+                    .setTimestampMicros(TIMESTAMP)
+                    .addLabels(LABEL)
+                    .build())
+                .build())
+            .addColumns(Column.newBuilder()
+                .setQualifier(QUALIFIER_2)
+                .addCells(Cell.newBuilder()
+                    .setValue(VALUE)
+                    .setTimestampMicros(TIMESTAMP)
+                    .addLabels(LABEL)
+                    .build())
+                .addCells(Cell.newBuilder()
+                    .setValue(VALUE)
+                    .setTimestampMicros(54321)
+                    .addLabels(LABEL)
+                    .build())
+                .build())
+            .build())
+        .addFamilies(Family.newBuilder()
+            .setName("secondFamily")
+            .addColumns(Column.newBuilder()
+                .setQualifier(QUALIFIER_1)
+                .addCells(Cell.newBuilder()
+                    .setValue(VALUE)
+                    .setTimestampMicros(TIMESTAMP)
+                    .addLabels(LABEL)
+                    .build())
+                .build()))
+        .build();
+    ReadModifyWriteRowResponse response =
+        ReadModifyWriteRowResponse.newBuilder().setRow(row).build();
+    ReadModifyWriteRow readModify = ReadModifyWriteRow.create(TABLE_ID, "test-key");
+    ReadModifyWriteRowRequest request = readModify.toProto(REQUEST_CONTEXT);
+    ImmutableList.Builder<RowCell> rowCells = ImmutableList.builder();
+    rowCells.add(RowCell.create("firstFamily", QUALIFIER_1, TIMESTAMP, LABEL_LIST, VALUE));
+    rowCells.add(RowCell.create("firstFamily", QUALIFIER_2, TIMESTAMP, LABEL_LIST, VALUE));
+    rowCells.add(RowCell.create("firstFamily", QUALIFIER_2, 54321, LABEL_LIST, VALUE));
+    rowCells.add(RowCell.create("secondFamily", QUALIFIER_1, TIMESTAMP, LABEL_LIST, VALUE));
+    com.google.cloud.bigtable.data.v2.models.Row expectedModelRow =
+        com.google.cloud.bigtable.data.v2.models.Row.create(ROW_KEY, rowCells.build());
+
+    when(client.readModifyWriteRow(request)).thenReturn(response);
+    com.google.cloud.bigtable.data.v2.models.Row actualRow =
+        clientWrapper.readModifyWriteRow(readModify);
+    assertEquals(expectedModelRow, actualRow);
+    verify(client).readModifyWriteRow(request);
   }
 }
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/scanner/TestFlatRowAdapter.java b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/scanner/TestFlatRowAdapter.java
new file mode 100644
index 000000000..fbe2eb06e
--- /dev/null
+++ b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/scanner/TestFlatRowAdapter.java
@@ -0,0 +1,163 @@
+/*
+ * 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.grpc.scanner;
+
+import com.google.cloud.bigtable.data.v2.models.RowAdapter;
+import com.google.common.collect.ImmutableList;
+
+import com.google.protobuf.ByteString;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TestFlatRowAdapter {
+
+  private final FlatRowAdapter adapter = new FlatRowAdapter();
+  private RowAdapter.RowBuilder<FlatRow> rowBuilder;
+
+  @Before
+  public void setUp() {
+    rowBuilder = adapter.createRowBuilder();
+  }
+
+  @Test
+  public void testWithSingleCellRow() {
+    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();
+
+    FlatRow expected = FlatRow.newBuilder()
+        .withRowKey(ByteString.copyFromUtf8("my-key"))
+        .addCell("my-family",
+            ByteString.copyFromUtf8("my-qualifier"),
+            100,
+            value,
+            ImmutableList.of("my-label")).build();
+
+    Assert.assertEquals(expected, rowBuilder.finishRow());
+  }
+
+  @Test
+  public void testWithMultiCell() {
+    FlatRow.Builder builder = FlatRow.newBuilder()
+        .withRowKey(ByteString.copyFromUtf8("my-key"));
+
+    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();
+
+      builder.addCell(
+          new FlatRow.Cell("family", qualifier, 1000, value, ImmutableList.of("my-label")));
+    }
+
+    Assert.assertEquals(builder.build(), rowBuilder.finishRow());
+  }
+
+  @Test
+  public void testWhenSplitCell() {
+    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();
+
+    FlatRow expected = FlatRow.newBuilder()
+        .withRowKey(ByteString.copyFromUtf8("my-key"))
+        .addCell("family",
+            ByteString.copyFromUtf8("qualifier"),
+            1000,
+            ByteString.copyFromUtf8("part1part2"),
+            ImmutableList.of("my-label")).build();
+
+    Assert.assertEquals(expected, rowBuilder.finishRow());
+  }
+
+  @Test
+  public void testWithMarkerRow() {
+    FlatRow markerRow = rowBuilder.createScanMarkerRow(ByteString.copyFromUtf8("key"));
+    Assert.assertTrue(adapter.isScanMarkerRow(markerRow));
+
+    ByteString value = ByteString.copyFromUtf8("value");
+    rowBuilder.startRow(ByteString.copyFromUtf8("key"));
+    rowBuilder.startCell(
+        "family", ByteString.EMPTY, 1000, ImmutableList.<String>of(), value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+
+    Assert.assertFalse(adapter.isScanMarkerRow(rowBuilder.finishRow()));
+  }
+
+  @Test
+  public void testFamilyOrdering(){
+    ByteString value = ByteString.copyFromUtf8("my-value");
+    ByteString qualifier = ByteString.copyFromUtf8("qualifier");
+    List<String> labels = ImmutableList.of("my-label");
+    String[] familyNames = {"aa", "bb", "ew", "fd", "zz"};
+    rowBuilder.startRow(ByteString.copyFromUtf8("firstKey"));
+    rowBuilder.startCell("zz", qualifier, 72, labels,
+        value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+    rowBuilder.startCell("bb", qualifier, 2309223, labels,
+        value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+    rowBuilder.startCell("aa", qualifier, 873, labels,
+        value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+    rowBuilder.startCell("fd", qualifier, 726, labels,
+        value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+    rowBuilder.startCell("ew", qualifier, System.currentTimeMillis(), labels,
+        value.size());
+    rowBuilder.cellValue(value);
+    rowBuilder.finishCell();
+
+    FlatRow row = rowBuilder.finishRow();
+    Assert.assertEquals("firstKey", row.getRowKey().toStringUtf8());
+    List<FlatRow.Cell> cells  = row.getCells();
+    for(int i =0; i<cells.size(); i++){
+      Assert.assertEquals(familyNames[i], cells.get(i).getFamily());
+    }
+  }
+}
diff --git a/bigtable-client-core-parent/bigtable-hbase-integration-tests-common/pom.xml b/bigtable-client-core-parent/bigtable-hbase-integration-tests-common/pom.xml
index 00f3d9e6e..e85cbfacb 100644
--- a/bigtable-client-core-parent/bigtable-hbase-integration-tests-common/pom.xml
+++ b/bigtable-client-core-parent/bigtable-hbase-integration-tests-common/pom.xml
@@ -19,7 +19,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-client-core-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-integration-tests-common</artifactId>
diff --git a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-integration-tests/pom.xml b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-integration-tests/pom.xml
index 101034bdb..1829f2b05 100644
--- a/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-integration-tests/pom.xml
+++ b/bigtable-hbase-1.x-parent/bigtable-hbase-1.x-integration-tests/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-hbase-1.x-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-1.x-integration-tests</artifactId>
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/pom.xml b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/pom.xml
index 761978fb1..ce72928d0 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/pom.xml
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <groupId>com.google.cloud.bigtable</groupId>
         <artifactId>bigtable-hbase-2.x-parent</artifactId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-hbase-2.x-integration-tests</artifactId>
diff --git a/bigtable-test/bigtable-emulator-maven-plugin/pom.xml b/bigtable-test/bigtable-emulator-maven-plugin/pom.xml
index 793b466c5..eddcb7eb6 100644
--- a/bigtable-test/bigtable-emulator-maven-plugin/pom.xml
+++ b/bigtable-test/bigtable-emulator-maven-plugin/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <artifactId>bigtable-test</artifactId>
         <groupId>com.google.cloud.bigtable</groupId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
 
diff --git a/bigtable-test/pom.xml b/bigtable-test/pom.xml
index 493f91655..719573df6 100644
--- a/bigtable-test/pom.xml
+++ b/bigtable-test/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
     <parent>
         <artifactId>bigtable-client-parent</artifactId>
         <groupId>com.google.cloud.bigtable</groupId>
-        <version>1.7.1-SNAPSHOT</version>
+        <version>1.8.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>bigtable-test</artifactId>