You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ap...@apache.org on 2014/09/25 01:06:20 UTC

[3/3] git commit: HBASE-11796 Add client support for atomic checkAndMutate (Srikanth Srungarapu)

HBASE-11796 Add client support for atomic checkAndMutate (Srikanth Srungarapu)


Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/be48e80d
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/be48e80d
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/be48e80d

Branch: refs/heads/0.98
Commit: be48e80d4014b375bf970dbab46846643d4b7ae1
Parents: b291f7e
Author: Andrew Purtell <ap...@apache.org>
Authored: Wed Sep 24 15:07:38 2014 -0700
Committer: Andrew Purtell <ap...@apache.org>
Committed: Wed Sep 24 15:52:07 2014 -0700

----------------------------------------------------------------------
 .../org/apache/hadoop/hbase/client/HTable.java  |  31 +-
 .../hadoop/hbase/client/HTableInterface.java    |  18 +
 .../apache/hadoop/hbase/client/HTablePool.java  |   8 +
 .../hadoop/hbase/protobuf/RequestConverter.java |  47 +++
 .../hbase/protobuf/generated/ClientProtos.java  | 370 ++++++++++++++++++-
 hbase-protocol/src/main/protobuf/Client.proto   |   3 +
 .../hbase/coprocessor/CoprocessorHost.java      |   9 +-
 .../hadoop/hbase/regionserver/HRegion.java      |  83 +++++
 .../hbase/regionserver/HRegionServer.java       |  62 +++-
 .../hadoop/hbase/rest/client/RemoteHTable.java  |   7 +
 .../hadoop/hbase/client/TestCheckAndMutate.java | 103 ++++++
 .../hadoop/hbase/regionserver/TestHRegion.java  |   2 +-
 12 files changed, 723 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
index e12c7a3..b00ff28 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
@@ -37,6 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
 import com.google.protobuf.InvalidProtocolBufferException;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -56,6 +57,7 @@ import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
 import org.apache.hadoop.hbase.filter.BinaryComparator;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.ipc.PayloadCarryingRpcController;
 import org.apache.hadoop.hbase.ipc.RegionCoprocessorRpcChannel;
@@ -73,7 +75,6 @@ import org.apache.hadoop.hbase.util.Pair;
 import org.apache.hadoop.hbase.util.Threads;
 
 import com.google.protobuf.Descriptors;
-import com.google.protobuf.GeneratedMessage;
 import com.google.protobuf.Message;
 import com.google.protobuf.Service;
 import com.google.protobuf.ServiceException;
@@ -1205,6 +1206,34 @@ public class HTable implements HTableInterface {
    * {@inheritDoc}
    */
   @Override
+  public boolean checkAndMutate(final byte [] row, final byte [] family, final byte [] qualifier,
+      final CompareOp compareOp, final byte [] value, final RowMutations rm)
+  throws IOException {
+    RegionServerCallable<Boolean> callable =
+        new RegionServerCallable<Boolean>(connection, getName(), row) {
+          @Override
+          public Boolean call() throws IOException {
+            PayloadCarryingRpcController controller = rpcControllerFactory.newController();
+            controller.setPriority(tableName);
+            try {
+              CompareType compareType = CompareType.valueOf(compareOp.name());
+              MultiRequest request = RequestConverter.buildMutateRequest(
+                  getLocation().getRegionInfo().getRegionName(), row, family, qualifier,
+                  new BinaryComparator(value), compareType, rm);
+              ClientProtos.MultiResponse response = getStub().multi(controller, request);
+              return Boolean.valueOf(response.getProcessed());
+            } catch (ServiceException se) {
+              throw ProtobufUtil.getRemoteException(se);
+            }
+          }
+        };
+    return rpcCallerFactory.<Boolean> newCaller().callWithRetries(callable, this.operationTimeout);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public boolean exists(final Get get) throws IOException {
     get.setCheckExistenceOnly(true);
     Result r = get(get);

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTableInterface.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTableInterface.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTableInterface.java
index eed5368..028b5bf 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTableInterface.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTableInterface.java
@@ -30,6 +30,7 @@ import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 
 import java.io.Closeable;
@@ -647,4 +648,21 @@ public interface HTableInterface extends Closeable {
   <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor methodDescriptor,
       Message request, byte[] startKey, byte[] endKey, R responsePrototype,
       Batch.Callback<R> callback) throws ServiceException, Throwable;
+
+  /**
+   * Atomically checks if a row/family/qualifier value matches the expected val
+   * If it does, it performs the row mutations.  If the passed value is null, t
+   * is for the lack of column (ie: non-existence)
+   *
+   * @param row to check
+   * @param family column family to check
+   * @param qualifier column qualifier to check
+   * @param compareOp the comparison operator
+   * @param value the expected value
+   * @param mutation  mutations to perform if check succeeds
+   * @throws IOException e
+   * @return true if the new put was executed, false otherwise
+   */
+  boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
+      byte[] value, RowMutations mutation) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTablePool.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTablePool.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTablePool.java
index 429b5f2..ad57bf2 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTablePool.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTablePool.java
@@ -31,6 +31,7 @@ import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.PoolMap;
@@ -643,5 +644,12 @@ public class HTablePool implements Closeable {
       checkState();
       table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype, callback);
     }
+
+    @Override
+    public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
+        byte[] value, RowMutations mutation) throws IOException {
+      checkState();
+      return table.checkAndMutate(row, family, qualifier, compareOp, value, mutation);
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java
index 7ce9254..756bc43 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java
@@ -100,6 +100,7 @@ import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.RunCatalogScanReq
 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetBalancerRunningRequest;
 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.UnassignRegionRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RegionServerStatusProtos.GetLastFlushedSequenceIdRequest;
+import org.apache.hadoop.hbase.util.ByteStringer;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.hadoop.hbase.util.Triple;
@@ -262,6 +263,52 @@ public final class RequestConverter {
   }
 
   /**
+   * Create a protocol buffer MutateRequest for conditioned row mutations
+   *
+   * @param regionName
+   * @param row
+   * @param family
+   * @param qualifier
+   * @param comparator
+   * @param compareType
+   * @param rowMutations
+   * @return a mutate request
+   * @throws IOException
+   */
+  public static ClientProtos.MultiRequest buildMutateRequest(
+      final byte[] regionName, final byte[] row, final byte[] family,
+      final byte [] qualifier, final ByteArrayComparable comparator,
+      final CompareType compareType, final RowMutations rowMutations) throws IOException {
+    RegionAction.Builder builder =
+        getRegionActionBuilderWithRegion(RegionAction.newBuilder(), regionName);
+    builder.setAtomic(true);
+    ClientProtos.Action.Builder actionBuilder = ClientProtos.Action.newBuilder();
+    MutationProto.Builder mutationBuilder = MutationProto.newBuilder();
+    Condition condition = buildCondition(
+        row, family, qualifier, comparator, compareType);
+    for (Mutation mutation: rowMutations.getMutations()) {
+      MutationType mutateType = null;
+      if (mutation instanceof Put) {
+        mutateType = MutationType.PUT;
+      } else if (mutation instanceof Delete) {
+        mutateType = MutationType.DELETE;
+      } else {
+        throw new DoNotRetryIOException("RowMutations supports only put and delete, not " +
+            mutation.getClass().getName());
+      }
+      mutationBuilder.clear();
+      MutationProto mp = ProtobufUtil.toMutation(mutateType, mutation, mutationBuilder);
+      actionBuilder.clear();
+      actionBuilder.setMutation(mp);
+      builder.addAction(actionBuilder.build());
+    }
+    ClientProtos.MultiRequest request =
+        ClientProtos.MultiRequest.newBuilder().addRegionAction(builder.build())
+            .setCondition(condition).build();
+    return request;
+  }
+
+  /**
    * Create a protocol buffer MutateRequest for a put
    *
    * @param regionName

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClientProtos.java
----------------------------------------------------------------------
diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClientProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClientProtos.java
index e70958c..ab6a68c 100644
--- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClientProtos.java
+++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClientProtos.java
@@ -27932,6 +27932,20 @@ public final class ClientProtos {
      * <code>optional uint64 nonceGroup = 2;</code>
      */
     long getNonceGroup();
+
+    // optional .Condition condition = 3;
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    boolean hasCondition();
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition getCondition();
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder getConditionOrBuilder();
   }
   /**
    * Protobuf type {@code MultiRequest}
@@ -28006,6 +28020,19 @@ public final class ClientProtos {
               nonceGroup_ = input.readUInt64();
               break;
             }
+            case 26: {
+              org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder subBuilder = null;
+              if (((bitField0_ & 0x00000002) == 0x00000002)) {
+                subBuilder = condition_.toBuilder();
+              }
+              condition_ = input.readMessage(org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.PARSER, extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(condition_);
+                condition_ = subBuilder.buildPartial();
+              }
+              bitField0_ |= 0x00000002;
+              break;
+            }
           }
         }
       } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -28101,9 +28128,32 @@ public final class ClientProtos {
       return nonceGroup_;
     }
 
+    // optional .Condition condition = 3;
+    public static final int CONDITION_FIELD_NUMBER = 3;
+    private org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition condition_;
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    public boolean hasCondition() {
+      return ((bitField0_ & 0x00000002) == 0x00000002);
+    }
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition getCondition() {
+      return condition_;
+    }
+    /**
+     * <code>optional .Condition condition = 3;</code>
+     */
+    public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder getConditionOrBuilder() {
+      return condition_;
+    }
+
     private void initFields() {
       regionAction_ = java.util.Collections.emptyList();
       nonceGroup_ = 0L;
+      condition_ = org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.getDefaultInstance();
     }
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
@@ -28116,6 +28166,12 @@ public final class ClientProtos {
           return false;
         }
       }
+      if (hasCondition()) {
+        if (!getCondition().isInitialized()) {
+          memoizedIsInitialized = 0;
+          return false;
+        }
+      }
       memoizedIsInitialized = 1;
       return true;
     }
@@ -28129,6 +28185,9 @@ public final class ClientProtos {
       if (((bitField0_ & 0x00000001) == 0x00000001)) {
         output.writeUInt64(2, nonceGroup_);
       }
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        output.writeMessage(3, condition_);
+      }
       getUnknownFields().writeTo(output);
     }
 
@@ -28146,6 +28205,10 @@ public final class ClientProtos {
         size += com.google.protobuf.CodedOutputStream
           .computeUInt64Size(2, nonceGroup_);
       }
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, condition_);
+      }
       size += getUnknownFields().getSerializedSize();
       memoizedSerializedSize = size;
       return size;
@@ -28176,6 +28239,11 @@ public final class ClientProtos {
         result = result && (getNonceGroup()
             == other.getNonceGroup());
       }
+      result = result && (hasCondition() == other.hasCondition());
+      if (hasCondition()) {
+        result = result && getCondition()
+            .equals(other.getCondition());
+      }
       result = result &&
           getUnknownFields().equals(other.getUnknownFields());
       return result;
@@ -28197,6 +28265,10 @@ public final class ClientProtos {
         hash = (37 * hash) + NONCEGROUP_FIELD_NUMBER;
         hash = (53 * hash) + hashLong(getNonceGroup());
       }
+      if (hasCondition()) {
+        hash = (37 * hash) + CONDITION_FIELD_NUMBER;
+        hash = (53 * hash) + getCondition().hashCode();
+      }
       hash = (29 * hash) + getUnknownFields().hashCode();
       memoizedHashCode = hash;
       return hash;
@@ -28308,6 +28380,7 @@ public final class ClientProtos {
       private void maybeForceBuilderInitialization() {
         if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
           getRegionActionFieldBuilder();
+          getConditionFieldBuilder();
         }
       }
       private static Builder create() {
@@ -28324,6 +28397,12 @@ public final class ClientProtos {
         }
         nonceGroup_ = 0L;
         bitField0_ = (bitField0_ & ~0x00000002);
+        if (conditionBuilder_ == null) {
+          condition_ = org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.getDefaultInstance();
+        } else {
+          conditionBuilder_.clear();
+        }
+        bitField0_ = (bitField0_ & ~0x00000004);
         return this;
       }
 
@@ -28365,6 +28444,14 @@ public final class ClientProtos {
           to_bitField0_ |= 0x00000001;
         }
         result.nonceGroup_ = nonceGroup_;
+        if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+          to_bitField0_ |= 0x00000002;
+        }
+        if (conditionBuilder_ == null) {
+          result.condition_ = condition_;
+        } else {
+          result.condition_ = conditionBuilder_.build();
+        }
         result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
@@ -28410,6 +28497,9 @@ public final class ClientProtos {
         if (other.hasNonceGroup()) {
           setNonceGroup(other.getNonceGroup());
         }
+        if (other.hasCondition()) {
+          mergeCondition(other.getCondition());
+        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -28421,6 +28511,12 @@ public final class ClientProtos {
             return false;
           }
         }
+        if (hasCondition()) {
+          if (!getCondition().isInitialized()) {
+            
+            return false;
+          }
+        }
         return true;
       }
 
@@ -28716,6 +28812,123 @@ public final class ClientProtos {
         return this;
       }
 
+      // optional .Condition condition = 3;
+      private org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition condition_ = org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.getDefaultInstance();
+      private com.google.protobuf.SingleFieldBuilder<
+          org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder> conditionBuilder_;
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public boolean hasCondition() {
+        return ((bitField0_ & 0x00000004) == 0x00000004);
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition getCondition() {
+        if (conditionBuilder_ == null) {
+          return condition_;
+        } else {
+          return conditionBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public Builder setCondition(org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition value) {
+        if (conditionBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          condition_ = value;
+          onChanged();
+        } else {
+          conditionBuilder_.setMessage(value);
+        }
+        bitField0_ |= 0x00000004;
+        return this;
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public Builder setCondition(
+          org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder builderForValue) {
+        if (conditionBuilder_ == null) {
+          condition_ = builderForValue.build();
+          onChanged();
+        } else {
+          conditionBuilder_.setMessage(builderForValue.build());
+        }
+        bitField0_ |= 0x00000004;
+        return this;
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public Builder mergeCondition(org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition value) {
+        if (conditionBuilder_ == null) {
+          if (((bitField0_ & 0x00000004) == 0x00000004) &&
+              condition_ != org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.getDefaultInstance()) {
+            condition_ =
+              org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.newBuilder(condition_).mergeFrom(value).buildPartial();
+          } else {
+            condition_ = value;
+          }
+          onChanged();
+        } else {
+          conditionBuilder_.mergeFrom(value);
+        }
+        bitField0_ |= 0x00000004;
+        return this;
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public Builder clearCondition() {
+        if (conditionBuilder_ == null) {
+          condition_ = org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.getDefaultInstance();
+          onChanged();
+        } else {
+          conditionBuilder_.clear();
+        }
+        bitField0_ = (bitField0_ & ~0x00000004);
+        return this;
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder getConditionBuilder() {
+        bitField0_ |= 0x00000004;
+        onChanged();
+        return getConditionFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder getConditionOrBuilder() {
+        if (conditionBuilder_ != null) {
+          return conditionBuilder_.getMessageOrBuilder();
+        } else {
+          return condition_;
+        }
+      }
+      /**
+       * <code>optional .Condition condition = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilder<
+          org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder> 
+          getConditionFieldBuilder() {
+        if (conditionBuilder_ == null) {
+          conditionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+              org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition.Builder, org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ConditionOrBuilder>(
+                  condition_,
+                  getParentForChildren(),
+                  isClean());
+          condition_ = null;
+        }
+        return conditionBuilder_;
+      }
+
       // @@protoc_insertion_point(builder_scope:MultiRequest)
     }
 
@@ -28754,6 +28967,24 @@ public final class ClientProtos {
      */
     org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResultOrBuilder getRegionActionResultOrBuilder(
         int index);
+
+    // optional bool processed = 2;
+    /**
+     * <code>optional bool processed = 2;</code>
+     *
+     * <pre>
+     * used for mutate to indicate processed only
+     * </pre>
+     */
+    boolean hasProcessed();
+    /**
+     * <code>optional bool processed = 2;</code>
+     *
+     * <pre>
+     * used for mutate to indicate processed only
+     * </pre>
+     */
+    boolean getProcessed();
   }
   /**
    * Protobuf type {@code MultiResponse}
@@ -28814,6 +29045,11 @@ public final class ClientProtos {
               regionActionResult_.add(input.readMessage(org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResult.PARSER, extensionRegistry));
               break;
             }
+            case 16: {
+              bitField0_ |= 0x00000001;
+              processed_ = input.readBool();
+              break;
+            }
           }
         }
       } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -28856,6 +29092,7 @@ public final class ClientProtos {
       return PARSER;
     }
 
+    private int bitField0_;
     // repeated .RegionActionResult regionActionResult = 1;
     public static final int REGIONACTIONRESULT_FIELD_NUMBER = 1;
     private java.util.List<org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResult> regionActionResult_;
@@ -28892,8 +29129,33 @@ public final class ClientProtos {
       return regionActionResult_.get(index);
     }
 
+    // optional bool processed = 2;
+    public static final int PROCESSED_FIELD_NUMBER = 2;
+    private boolean processed_;
+    /**
+     * <code>optional bool processed = 2;</code>
+     *
+     * <pre>
+     * used for mutate to indicate processed only
+     * </pre>
+     */
+    public boolean hasProcessed() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional bool processed = 2;</code>
+     *
+     * <pre>
+     * used for mutate to indicate processed only
+     * </pre>
+     */
+    public boolean getProcessed() {
+      return processed_;
+    }
+
     private void initFields() {
       regionActionResult_ = java.util.Collections.emptyList();
+      processed_ = false;
     }
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
@@ -28916,6 +29178,9 @@ public final class ClientProtos {
       for (int i = 0; i < regionActionResult_.size(); i++) {
         output.writeMessage(1, regionActionResult_.get(i));
       }
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        output.writeBool(2, processed_);
+      }
       getUnknownFields().writeTo(output);
     }
 
@@ -28929,6 +29194,10 @@ public final class ClientProtos {
         size += com.google.protobuf.CodedOutputStream
           .computeMessageSize(1, regionActionResult_.get(i));
       }
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(2, processed_);
+      }
       size += getUnknownFields().getSerializedSize();
       memoizedSerializedSize = size;
       return size;
@@ -28954,6 +29223,11 @@ public final class ClientProtos {
       boolean result = true;
       result = result && getRegionActionResultList()
           .equals(other.getRegionActionResultList());
+      result = result && (hasProcessed() == other.hasProcessed());
+      if (hasProcessed()) {
+        result = result && (getProcessed()
+            == other.getProcessed());
+      }
       result = result &&
           getUnknownFields().equals(other.getUnknownFields());
       return result;
@@ -28971,6 +29245,10 @@ public final class ClientProtos {
         hash = (37 * hash) + REGIONACTIONRESULT_FIELD_NUMBER;
         hash = (53 * hash) + getRegionActionResultList().hashCode();
       }
+      if (hasProcessed()) {
+        hash = (37 * hash) + PROCESSED_FIELD_NUMBER;
+        hash = (53 * hash) + hashBoolean(getProcessed());
+      }
       hash = (29 * hash) + getUnknownFields().hashCode();
       memoizedHashCode = hash;
       return hash;
@@ -29087,6 +29365,8 @@ public final class ClientProtos {
         } else {
           regionActionResultBuilder_.clear();
         }
+        processed_ = false;
+        bitField0_ = (bitField0_ & ~0x00000002);
         return this;
       }
 
@@ -29114,6 +29394,7 @@ public final class ClientProtos {
       public org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiResponse buildPartial() {
         org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiResponse result = new org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiResponse(this);
         int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
         if (regionActionResultBuilder_ == null) {
           if (((bitField0_ & 0x00000001) == 0x00000001)) {
             regionActionResult_ = java.util.Collections.unmodifiableList(regionActionResult_);
@@ -29123,6 +29404,11 @@ public final class ClientProtos {
         } else {
           result.regionActionResult_ = regionActionResultBuilder_.build();
         }
+        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+          to_bitField0_ |= 0x00000001;
+        }
+        result.processed_ = processed_;
+        result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
       }
@@ -29164,6 +29450,9 @@ public final class ClientProtos {
             }
           }
         }
+        if (other.hasProcessed()) {
+          setProcessed(other.getProcessed());
+        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -29437,6 +29726,55 @@ public final class ClientProtos {
         return regionActionResultBuilder_;
       }
 
+      // optional bool processed = 2;
+      private boolean processed_ ;
+      /**
+       * <code>optional bool processed = 2;</code>
+       *
+       * <pre>
+       * used for mutate to indicate processed only
+       * </pre>
+       */
+      public boolean hasProcessed() {
+        return ((bitField0_ & 0x00000002) == 0x00000002);
+      }
+      /**
+       * <code>optional bool processed = 2;</code>
+       *
+       * <pre>
+       * used for mutate to indicate processed only
+       * </pre>
+       */
+      public boolean getProcessed() {
+        return processed_;
+      }
+      /**
+       * <code>optional bool processed = 2;</code>
+       *
+       * <pre>
+       * used for mutate to indicate processed only
+       * </pre>
+       */
+      public Builder setProcessed(boolean value) {
+        bitField0_ |= 0x00000002;
+        processed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional bool processed = 2;</code>
+       *
+       * <pre>
+       * used for mutate to indicate processed only
+       * </pre>
+       */
+      public Builder clearProcessed() {
+        bitField0_ = (bitField0_ & ~0x00000002);
+        processed_ = false;
+        onChanged();
+        return this;
+      }
+
       // @@protoc_insertion_point(builder_scope:MultiResponse)
     }
 
@@ -30277,20 +30615,22 @@ public final class ClientProtos {
       "air\0221\n\016service_result\030\004 \001(\0132\031.Coprocesso" +
       "rServiceResult\"f\n\022RegionActionResult\022-\n\021" +
       "resultOrException\030\001 \003(\0132\022.ResultOrExcept" +
-      "ion\022!\n\texception\030\002 \001(\0132\016.NameBytesPair\"G" +
+      "ion\022!\n\texception\030\002 \001(\0132\016.NameBytesPair\"f" +
       "\n\014MultiRequest\022#\n\014regionAction\030\001 \003(\0132\r.R",
-      "egionAction\022\022\n\nnonceGroup\030\002 \001(\004\"@\n\rMulti" +
-      "Response\022/\n\022regionActionResult\030\001 \003(\0132\023.R" +
-      "egionActionResult2\261\002\n\rClientService\022 \n\003G" +
-      "et\022\013.GetRequest\032\014.GetResponse\022)\n\006Mutate\022" +
-      "\016.MutateRequest\032\017.MutateResponse\022#\n\004Scan" +
-      "\022\014.ScanRequest\032\r.ScanResponse\022>\n\rBulkLoa" +
-      "dHFile\022\025.BulkLoadHFileRequest\032\026.BulkLoad" +
-      "HFileResponse\022F\n\013ExecService\022\032.Coprocess" +
-      "orServiceRequest\032\033.CoprocessorServiceRes" +
-      "ponse\022&\n\005Multi\022\r.MultiRequest\032\016.MultiRes",
-      "ponseBB\n*org.apache.hadoop.hbase.protobu" +
-      "f.generatedB\014ClientProtosH\001\210\001\001\240\001\001"
+      "egionAction\022\022\n\nnonceGroup\030\002 \001(\004\022\035\n\tcondi" +
+      "tion\030\003 \001(\0132\n.Condition\"S\n\rMultiResponse\022" +
+      "/\n\022regionActionResult\030\001 \003(\0132\023.RegionActi" +
+      "onResult\022\021\n\tprocessed\030\002 \001(\0102\261\002\n\rClientSe" +
+      "rvice\022 \n\003Get\022\013.GetRequest\032\014.GetResponse\022" +
+      ")\n\006Mutate\022\016.MutateRequest\032\017.MutateRespon" +
+      "se\022#\n\004Scan\022\014.ScanRequest\032\r.ScanResponse\022" +
+      ">\n\rBulkLoadHFile\022\025.BulkLoadHFileRequest\032" +
+      "\026.BulkLoadHFileResponse\022F\n\013ExecService\022\032" +
+      ".CoprocessorServiceRequest\032\033.Coprocessor",
+      "ServiceResponse\022&\n\005Multi\022\r.MultiRequest\032" +
+      "\016.MultiResponseBB\n*org.apache.hadoop.hba" +
+      "se.protobuf.generatedB\014ClientProtosH\001\210\001\001" +
+      "\240\001\001"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
       new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -30464,13 +30804,13 @@ public final class ClientProtos {
           internal_static_MultiRequest_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessage.FieldAccessorTable(
               internal_static_MultiRequest_descriptor,
-              new java.lang.String[] { "RegionAction", "NonceGroup", });
+              new java.lang.String[] { "RegionAction", "NonceGroup", "Condition", });
           internal_static_MultiResponse_descriptor =
             getDescriptor().getMessageTypes().get(25);
           internal_static_MultiResponse_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessage.FieldAccessorTable(
               internal_static_MultiResponse_descriptor,
-              new java.lang.String[] { "RegionActionResult", });
+              new java.lang.String[] { "RegionActionResult", "Processed", });
           return null;
         }
       };

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-protocol/src/main/protobuf/Client.proto
----------------------------------------------------------------------
diff --git a/hbase-protocol/src/main/protobuf/Client.proto b/hbase-protocol/src/main/protobuf/Client.proto
index 9f8affd..537c1d4 100644
--- a/hbase-protocol/src/main/protobuf/Client.proto
+++ b/hbase-protocol/src/main/protobuf/Client.proto
@@ -372,10 +372,13 @@ message RegionActionResult {
 message MultiRequest {
   repeated RegionAction regionAction = 1;
   optional uint64 nonceGroup = 2;
+  optional Condition condition = 3;
 }
 
 message MultiResponse {
   repeated RegionActionResult regionActionResult = 1;
+  // used for mutate to indicate processed only
+  optional bool processed = 2;
 }
 
 

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
index 80ef9d5..e23a75a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
@@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.google.protobuf.Descriptors;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -64,6 +65,7 @@ import org.apache.hadoop.hbase.client.RowMutations;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
@@ -71,7 +73,6 @@ import org.apache.hadoop.hbase.util.SortedCopyOnWriteSet;
 import org.apache.hadoop.hbase.util.VersionInfo;
 import org.apache.hadoop.io.MultipleIOException;
 
-import com.google.protobuf.Descriptors.ServiceDescriptor;
 import com.google.protobuf.Message;
 import com.google.protobuf.Service;
 import com.google.protobuf.ServiceException;
@@ -633,6 +634,12 @@ public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
         table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype,
             callback);
       }
+
+      @Override
+      public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
+          CompareOp compareOp, byte[] value, RowMutations mutation) throws IOException {
+        return table.checkAndMutate(row, family, qualifier, compareOp, value, mutation);
+      }
     }
 
     /** The coprocessor */

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
index 8b3954b..01dda60 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
@@ -2786,6 +2786,89 @@ public class HRegion implements HeapSize { // , Writable{
     }
   }
 
+  //TODO, Think that gets/puts and deletes should be refactored a bit so that
+  //the getting of the lock happens before, so that you would just pass it into
+  //the methods. So in the case of checkAndMutate you could just do lockRow,
+  //get, put, unlockRow or something
+  /**
+   *
+   * @throws IOException
+   * @return true if the new put was executed, false otherwise
+   */
+  public boolean checkAndRowMutate(byte [] row, byte [] family, byte [] qualifier,
+      CompareOp compareOp, ByteArrayComparable comparator, RowMutations rm,
+      boolean writeToWAL)
+      throws IOException{
+    checkReadOnly();
+    //TODO, add check for value length or maybe even better move this to the
+    //client if this becomes a global setting
+    checkResources();
+
+    startRegionOperation();
+    try {
+      Get get = new Get(row);
+      checkFamily(family);
+      get.addColumn(family, qualifier);
+
+      // Lock row - note that doBatchMutate will relock this row if called
+      RowLock rowLock = getRowLock(get.getRow());
+      // wait for all previous transactions to complete (with lock held)
+      mvcc.completeMemstoreInsert(mvcc.beginMemstoreInsert());
+      try {
+        List<Cell> result = get(get, false);
+
+        boolean valueIsNull = comparator.getValue() == null ||
+            comparator.getValue().length == 0;
+        boolean matches = false;
+        if (result.size() == 0 && valueIsNull) {
+          matches = true;
+        } else if (result.size() > 0 && result.get(0).getValueLength() == 0 &&
+            valueIsNull) {
+          matches = true;
+        } else if (result.size() == 1 && !valueIsNull) {
+          Cell kv = result.get(0);
+          int compareResult = comparator.compareTo(kv.getValueArray(),
+              kv.getValueOffset(), kv.getValueLength());
+          switch (compareOp) {
+          case LESS:
+            matches = compareResult < 0;
+            break;
+          case LESS_OR_EQUAL:
+            matches = compareResult <= 0;
+            break;
+          case EQUAL:
+            matches = compareResult == 0;
+            break;
+          case NOT_EQUAL:
+            matches = compareResult != 0;
+            break;
+          case GREATER_OR_EQUAL:
+            matches = compareResult >= 0;
+            break;
+          case GREATER:
+            matches = compareResult > 0;
+            break;
+          default:
+            throw new RuntimeException("Unknown Compare op " + compareOp.name());
+          }
+        }
+        //If matches put the new put or delete the new delete
+        if (matches) {
+          // All edits for the given row (across all column families) must
+          // happen atomically.
+          mutateRow(rm);
+          this.checkAndMutateChecksPassed.increment();
+          return true;
+        }
+        this.checkAndMutateChecksFailed.increment();
+        return false;
+      } finally {
+        rowLock.release();
+      }
+    } finally {
+      closeRegionOperation();
+    }
+  }
   private void doBatchMutate(Mutation mutation) throws IOException, DoNotRetryIOException {
     // Currently this is only called for puts and deletes, so no nonces.
     OperationStatus[] batchMutate = this.batchMutate(new Mutation[] { mutation },

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index 9caddd5..7639b48 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -3428,6 +3428,7 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa
     List<CellScannable> cellsToReturn = null;
     MultiResponse.Builder responseBuilder = MultiResponse.newBuilder();
     RegionActionResult.Builder regionActionResultBuilder = RegionActionResult.newBuilder();
+    Boolean processed = null;
 
     for (RegionAction regionAction : request.getRegionActionList()) {
       this.requestCount.add(regionAction.getActionCount());
@@ -3445,7 +3446,20 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa
         // How does this call happen?  It may need some work to play well w/ the surroundings.
         // Need to return an item per Action along w/ Action index.  TODO.
         try {
-          mutateRows(region, regionAction.getActionList(), cellScanner);
+          if (request.hasCondition()) {
+            Condition condition = request.getCondition();
+            byte[] row = condition.getRow().toByteArray();
+            byte[] family = condition.getFamily().toByteArray();
+            byte[] qualifier = condition.getQualifier().toByteArray();
+            CompareOp compareOp = CompareOp.valueOf(condition.getCompareType().name());
+            ByteArrayComparable comparator =
+              ProtobufUtil.toComparator(condition.getComparator());
+            processed = checkAndRowMutate(region, regionAction.getActionList(),
+              cellScanner, row, family, qualifier, compareOp, comparator);
+          } else {
+            mutateRows(region, regionAction.getActionList(), cellScanner);
+            processed = Boolean.TRUE;
+          }
         } catch (IOException e) {
           // As it's atomic, we may expect it's a global failure.
           regionActionResultBuilder.setException(ResponseConverter.buildException(e));
@@ -3461,6 +3475,7 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa
     if (cellsToReturn != null && !cellsToReturn.isEmpty() && controller != null) {
       controller.setCellScanner(CellUtil.createCellScanner(cellsToReturn));
     }
+    if (processed != null) responseBuilder.setProcessed(processed);
     return responseBuilder.build();
   }
 
@@ -4465,7 +4480,7 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa
    *
    * @param region
    * @param actions
- * @param cellScanner if non-null, the mutation data -- the Cell content.
+   * @param cellScanner if non-null, the mutation data -- the Cell content.
    * @throws IOException
    */
   protected void mutateRows(final HRegion region, final List<ClientProtos.Action> actions,
@@ -4498,6 +4513,49 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa
     region.mutateRow(rm);
   }
 
+  /**
+   * Mutate a list of rows atomically.
+   *
+   * @param region
+   * @param actions
+   * @param cellScanner if non-null, the mutation data -- the Cell content.
+   * @param row
+   * @param family
+   * @param qualifier
+   * @param compareOp
+   * @param comparator
+   * @throws IOException
+   */
+  private boolean checkAndRowMutate(final HRegion region, final List<ClientProtos.Action> actions,
+      final CellScanner cellScanner, byte[] row, byte[] family, byte[] qualifier,
+      CompareOp compareOp, ByteArrayComparable comparator) throws IOException {
+    if (!region.getRegionInfo().isMetaTable()) {
+      cacheFlusher.reclaimMemStoreMemory();
+    }
+    RowMutations rm = null;
+    for (ClientProtos.Action action: actions) {
+      if (action.hasGet()) {
+        throw new DoNotRetryIOException("Atomic put and/or delete only, not a Get=" +
+            action.getGet());
+      }
+      MutationType type = action.getMutation().getMutateType();
+      if (rm == null) {
+        rm = new RowMutations(action.getMutation().getRow().toByteArray());
+      }
+      switch (type) {
+      case PUT:
+        rm.add(ProtobufUtil.toPut(action.getMutation(), cellScanner));
+        break;
+      case DELETE:
+        rm.add(ProtobufUtil.toDelete(action.getMutation(), cellScanner));
+        break;
+      default:
+        throw new DoNotRetryIOException("Atomic put and/or delete only, not " + type.name());
+      }
+    }
+    return region.checkAndRowMutate(row, family, qualifier, compareOp, comparator, rm, Boolean.TRUE);
+  }
+
   private static class MovedRegionInfo {
     private final ServerName serverName;
     private final long seqNum;

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java
index 764529c..fbede44 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java
@@ -54,6 +54,7 @@ import org.apache.hadoop.hbase.client.RowMutations;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.io.TimeRange;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.rest.Constants;
@@ -815,4 +816,10 @@ public class RemoteHTable implements HTableInterface {
       throws ServiceException, Throwable {
     throw new UnsupportedOperationException("batchCoprocessorService not implemented");
   }
+
+  @Override
+  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
+      byte[] value, RowMutations mutation) throws IOException {
+    throw new UnsupportedOperationException("checkAndMutate not implemented");
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndMutate.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndMutate.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndMutate.java
new file mode 100644
index 0000000..1203e1f
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndMutate.java
@@ -0,0 +1,103 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://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 org.apache.hadoop.hbase.client;
+
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.MediumTests;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.filter.CompareFilter;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import static org.junit.Assert.assertTrue;
+
+@Category(MediumTests.class)
+public class TestCheckAndMutate {
+  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    TEST_UTIL.startMiniCluster();
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @AfterClass
+  public static void tearDownAfterClass() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testCheckAndMutate() throws Exception {
+    final TableName tableName = TableName.valueOf("TestPutWithDelete");
+    final byte[] rowKey = Bytes.toBytes("12345");
+    final byte[] family = Bytes.toBytes("cf");
+    HTable table = TEST_UTIL.createTable(tableName, family);
+    TEST_UTIL.waitTableAvailable(tableName.getName(), 5000);
+    try {
+      // put one row
+      Put put = new Put(rowKey);
+      put.add(family, Bytes.toBytes("A"), Bytes.toBytes("a"));
+      put.add(family, Bytes.toBytes("B"), Bytes.toBytes("b"));
+      put.add(family, Bytes.toBytes("C"), Bytes.toBytes("c"));
+      table.put(put);
+      // get row back and assert the values
+      Get get = new Get(rowKey);
+      Result result = table.get(get);
+      assertTrue("Column A value should be a",
+          Bytes.toString(result.getValue(family, Bytes.toBytes("A"))).equals("a"));
+      assertTrue("Column B value should be b",
+          Bytes.toString(result.getValue(family, Bytes.toBytes("B"))).equals("b"));
+      assertTrue("Column C value should be c",
+          Bytes.toString(result.getValue(family, Bytes.toBytes("C"))).equals("c"));
+
+      // put the same row again with C column deleted
+      RowMutations rm = new RowMutations(rowKey);
+      put = new Put(rowKey);
+      put.add(family, Bytes.toBytes("A"), Bytes.toBytes("a"));
+      put.add(family, Bytes.toBytes("B"), Bytes.toBytes("b"));
+      rm.add(put);
+      Delete del = new Delete(rowKey);
+      del.deleteColumn(family, Bytes.toBytes("C"));
+      rm.add(del);
+      boolean res = table.checkAndMutate(rowKey, family, Bytes.toBytes("A"), CompareFilter.CompareOp.EQUAL,
+          Bytes.toBytes("a"), rm);
+      assertTrue(res);
+
+      // get row back and assert the values
+      get = new Get(rowKey);
+      result = table.get(get);
+      assertTrue("Column A value should be a",
+          Bytes.toString(result.getValue(family, Bytes.toBytes("A"))).equals("a"));
+      assertTrue("Column B value should be b",
+          Bytes.toString(result.getValue(family, Bytes.toBytes("B"))).equals("b"));
+      assertTrue("Column C should not exist",
+          result.getValue(family, Bytes.toBytes("C")) == null);
+    } finally {
+      table.close();
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/hbase/blob/be48e80d/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
index c4b9f33..a26aeb5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
@@ -1222,7 +1222,7 @@ public class TestHRegion {
       Delete delete = new Delete(row1);
       delete.deleteFamily(fam1);
       res = region.checkAndMutate(row1, fam1, qf1, CompareOp.EQUAL, new BinaryComparator(val2),
-          delete, true);
+          put, true);
       assertEquals(false, res);
     } finally {
       HRegion.closeHRegion(this.region);