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:19 UTC

[2/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/dab2af79
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/dab2af79
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/dab2af79

Branch: refs/heads/branch-1
Commit: dab2af79ea9e4f022bf3b2d713fa0c6520ad7e03
Parents: 5e096c5
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:20:53 2014 -0700

----------------------------------------------------------------------
 .../org/apache/hadoop/hbase/client/HTable.java  |  29 ++
 .../apache/hadoop/hbase/client/HTablePool.java  |   8 +-
 .../org/apache/hadoop/hbase/client/Table.java   |  30 +-
 .../hadoop/hbase/protobuf/RequestConverter.java |  47 +++
 .../hbase/protobuf/generated/ClientProtos.java  | 372 ++++++++++++++++++-
 hbase-protocol/src/main/protobuf/Client.proto   |   3 +
 .../hadoop/hbase/client/HTableWrapper.java      |  39 +-
 .../hadoop/hbase/regionserver/HRegion.java      |  83 +++++
 .../hbase/regionserver/RSRpcServices.java       | 102 +++--
 .../hadoop/hbase/rest/client/RemoteHTable.java  |  32 +-
 .../hadoop/hbase/client/TestCheckAndMutate.java | 103 +++++
 .../hadoop/hbase/regionserver/TestHRegion.java  |   2 +-
 12 files changed, 767 insertions(+), 83 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/dab2af79/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 e19272b..26da937 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
@@ -1325,6 +1325,35 @@ public class HTable implements HTableInterface, RegionLocator {
    * {@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(int callTimeout) throws IOException {
+            PayloadCarryingRpcController controller = rpcControllerFactory.newController();
+            controller.setPriority(tableName);
+            controller.setCallTimeout(callTimeout);
+            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/dab2af79/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 ff1ae75..4b998a6 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,7 +31,6 @@ 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.BinaryComparator;
 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -666,5 +665,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/dab2af79/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Table.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Table.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Table.java
index acae891..0992295 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Table.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Table.java
@@ -18,6 +18,11 @@
  */
 package org.apache.hadoop.hbase.client;
 
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
 import com.google.protobuf.Descriptors;
 import com.google.protobuf.Message;
 import com.google.protobuf.Service;
@@ -31,10 +36,10 @@ import org.apache.hadoop.hbase.client.coprocessor.Batch;
 import org.apache.hadoop.hbase.filter.CompareFilter;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.Message;
+import com.google.protobuf.Service;
+import com.google.protobuf.ServiceException;
 
 /**
  * Used to communicate with a single HBase table.
@@ -598,4 +603,21 @@ public interface Table 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 value.
+   * If it does, it performs the row mutations.  If the passed value is null, the check
+   * 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,
+      CompareFilter.CompareOp compareOp, byte[] value, RowMutations mutation) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/dab2af79/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 fb77909..f5c128b 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
@@ -101,6 +101,7 @@ import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetBalancerRunnin
 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.TruncateTableRequest;
 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;
@@ -263,6 +264,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/dab2af79/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 c197eb7..93f202e 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
@@ -28429,6 +28429,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}
@@ -28503,6 +28517,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) {
@@ -28598,9 +28625,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() {
@@ -28613,6 +28663,12 @@ public final class ClientProtos {
           return false;
         }
       }
+      if (hasCondition()) {
+        if (!getCondition().isInitialized()) {
+          memoizedIsInitialized = 0;
+          return false;
+        }
+      }
       memoizedIsInitialized = 1;
       return true;
     }
@@ -28626,6 +28682,9 @@ public final class ClientProtos {
       if (((bitField0_ & 0x00000001) == 0x00000001)) {
         output.writeUInt64(2, nonceGroup_);
       }
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        output.writeMessage(3, condition_);
+      }
       getUnknownFields().writeTo(output);
     }
 
@@ -28643,6 +28702,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;
@@ -28673,6 +28736,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;
@@ -28694,6 +28762,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;
@@ -28805,6 +28877,7 @@ public final class ClientProtos {
       private void maybeForceBuilderInitialization() {
         if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
           getRegionActionFieldBuilder();
+          getConditionFieldBuilder();
         }
       }
       private static Builder create() {
@@ -28821,6 +28894,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;
       }
 
@@ -28862,6 +28941,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;
@@ -28907,6 +28994,9 @@ public final class ClientProtos {
         if (other.hasNonceGroup()) {
           setNonceGroup(other.getNonceGroup());
         }
+        if (other.hasCondition()) {
+          mergeCondition(other.getCondition());
+        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -28918,6 +29008,12 @@ public final class ClientProtos {
             return false;
           }
         }
+        if (hasCondition()) {
+          if (!getCondition().isInitialized()) {
+            
+            return false;
+          }
+        }
         return true;
       }
 
@@ -29213,6 +29309,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)
     }
 
@@ -29251,6 +29464,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}
@@ -29311,6 +29542,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) {
@@ -29353,6 +29589,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_;
@@ -29389,8 +29626,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() {
@@ -29413,6 +29675,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);
     }
 
@@ -29426,6 +29691,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;
@@ -29451,6 +29720,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;
@@ -29468,6 +29742,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;
@@ -29584,6 +29862,8 @@ public final class ClientProtos {
         } else {
           regionActionResultBuilder_.clear();
         }
+        processed_ = false;
+        bitField0_ = (bitField0_ & ~0x00000002);
         return this;
       }
 
@@ -29611,6 +29891,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_);
@@ -29620,6 +29901,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;
       }
@@ -29661,6 +29947,9 @@ public final class ClientProtos {
             }
           }
         }
+        if (other.hasProcessed()) {
+          setProcessed(other.getProcessed());
+        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -29934,6 +30223,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)
     }
 
@@ -30778,20 +31116,22 @@ public final class ClientProtos {
       "essorServiceResult\"f\n\022RegionActionResult",
       "\022-\n\021resultOrException\030\001 \003(\0132\022.ResultOrEx" +
       "ception\022!\n\texception\030\002 \001(\0132\016.NameBytesPa" +
-      "ir\"G\n\014MultiRequest\022#\n\014regionAction\030\001 \003(\013" +
-      "2\r.RegionAction\022\022\n\nnonceGroup\030\002 \001(\004\"@\n\rM" +
-      "ultiResponse\022/\n\022regionActionResult\030\001 \003(\013" +
-      "2\023.RegionActionResult*\'\n\013Consistency\022\n\n\006" +
-      "STRONG\020\000\022\014\n\010TIMELINE\020\0012\261\002\n\rClientService" +
-      "\022 \n\003Get\022\013.GetRequest\032\014.GetResponse\022)\n\006Mu" +
-      "tate\022\016.MutateRequest\032\017.MutateResponse\022#\n" +
-      "\004Scan\022\014.ScanRequest\032\r.ScanResponse\022>\n\rBu",
-      "lkLoadHFile\022\025.BulkLoadHFileRequest\032\026.Bul" +
-      "kLoadHFileResponse\022F\n\013ExecService\022\032.Copr" +
-      "ocessorServiceRequest\032\033.CoprocessorServi" +
-      "ceResponse\022&\n\005Multi\022\r.MultiRequest\032\016.Mul" +
-      "tiResponseBB\n*org.apache.hadoop.hbase.pr" +
-      "otobuf.generatedB\014ClientProtosH\001\210\001\001\240\001\001"
+      "ir\"f\n\014MultiRequest\022#\n\014regionAction\030\001 \003(\013" +
+      "2\r.RegionAction\022\022\n\nnonceGroup\030\002 \001(\004\022\035\n\tc" +
+      "ondition\030\003 \001(\0132\n.Condition\"S\n\rMultiRespo" +
+      "nse\022/\n\022regionActionResult\030\001 \003(\0132\023.Region" +
+      "ActionResult\022\021\n\tprocessed\030\002 \001(\010*\'\n\013Consi" +
+      "stency\022\n\n\006STRONG\020\000\022\014\n\010TIMELINE\020\0012\261\002\n\rCli" +
+      "entService\022 \n\003Get\022\013.GetRequest\032\014.GetResp" +
+      "onse\022)\n\006Mutate\022\016.MutateRequest\032\017.MutateR",
+      "esponse\022#\n\004Scan\022\014.ScanRequest\032\r.ScanResp" +
+      "onse\022>\n\rBulkLoadHFile\022\025.BulkLoadHFileReq" +
+      "uest\032\026.BulkLoadHFileResponse\022F\n\013ExecServ" +
+      "ice\022\032.CoprocessorServiceRequest\032\033.Coproc" +
+      "essorServiceResponse\022&\n\005Multi\022\r.MultiReq" +
+      "uest\032\016.MultiResponseBB\n*org.apache.hadoo" +
+      "p.hbase.protobuf.generatedB\014ClientProtos" +
+      "H\001\210\001\001\240\001\001"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
       new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -30965,13 +31305,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/dab2af79/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 9eedd1b..5c8c104 100644
--- a/hbase-protocol/src/main/protobuf/Client.proto
+++ b/hbase-protocol/src/main/protobuf/Client.proto
@@ -387,10 +387,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/dab2af79/hbase-server/src/main/java/org/apache/hadoop/hbase/client/HTableWrapper.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/HTableWrapper.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/HTableWrapper.java
index a3ab6fa..afc8a09 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/HTableWrapper.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/HTableWrapper.java
@@ -18,29 +18,13 @@
  */
 package org.apache.hadoop.hbase.client;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-
+import com.google.protobuf.Descriptors.MethodDescriptor;
+import com.google.protobuf.Message;
+import com.google.protobuf.Service;
+import com.google.protobuf.ServiceException;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.client.Append;
-import org.apache.hadoop.hbase.client.ClusterConnection;
-import org.apache.hadoop.hbase.client.Delete;
-import org.apache.hadoop.hbase.client.Durability;
-import org.apache.hadoop.hbase.client.Get;
-import org.apache.hadoop.hbase.client.HTable;
-import org.apache.hadoop.hbase.client.HTableInterface;
-import org.apache.hadoop.hbase.client.Increment;
-import org.apache.hadoop.hbase.client.Put;
-import org.apache.hadoop.hbase.client.Result;
-import org.apache.hadoop.hbase.client.ResultScanner;
-import org.apache.hadoop.hbase.client.Row;
-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.coprocessor.CoprocessorHost.Environment;
@@ -48,10 +32,11 @@ import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.io.MultipleIOException;
 
-import com.google.protobuf.Descriptors.MethodDescriptor;
-import com.google.protobuf.Message;
-import com.google.protobuf.Service;
-import com.google.protobuf.ServiceException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
 
 /**
  * A wrapper for HTable. Can be used to restrict privilege.
@@ -353,4 +338,10 @@ public class HTableWrapper implements HTableInterface {
     table.batchCoprocessorService(methodDescriptor, request, startKey, endKey, responsePrototype,
       callback);
   }
+
+  @Override
+  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
+      CompareOp compareOp, byte[] value, RowMutations rm) throws IOException {
+    return table.checkAndMutate(row, family, qualifier, compareOp, value, rm);
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/dab2af79/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 04165c5..c6469f3 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
@@ -2973,6 +2973,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.waitForPreviousTransactionsComplete();
+      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/dab2af79/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
index 041e965..98f961f 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
@@ -18,23 +18,11 @@
  */
 package org.apache.hadoop.hbase.regionserver;
 
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Message;
+import com.google.protobuf.RpcController;
+import com.google.protobuf.ServiceException;
+import com.google.protobuf.TextFormat;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -163,11 +151,22 @@ import org.apache.hadoop.hbase.zookeeper.ZKSplitLog;
 import org.apache.hadoop.net.DNS;
 import org.apache.zookeeper.KeeperException;
 
-import com.google.protobuf.ByteString;
-import com.google.protobuf.Message;
-import com.google.protobuf.RpcController;
-import com.google.protobuf.ServiceException;
-import com.google.protobuf.TextFormat;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Implements the regionserver RPC services.
@@ -384,6 +383,48 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
   }
 
   /**
+   * 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()) {
+      regionServer.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);
+  }
+
+  /**
    * Execute an append mutation.
    *
    * @param region
@@ -1705,6 +1746,7 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
     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());
@@ -1722,7 +1764,20 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
         // 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));
@@ -1738,6 +1793,7 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
     if (cellsToReturn != null && !cellsToReturn.isEmpty() && controller != null) {
       controller.setCellScanner(CellUtil.createCellScanner(cellsToReturn));
     }
+    if (processed != null) responseBuilder.setProcessed(processed);
     return responseBuilder.build();
   }
 

http://git-wip-us.apache.org/repos/asf/hbase/blob/dab2af79/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 9be0dd8..65bf509 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
@@ -19,16 +19,10 @@
 
 package org.apache.hadoop.hbase.rest.client;
 
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.Message;
+import com.google.protobuf.Service;
+import com.google.protobuf.ServiceException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -67,10 +61,15 @@ import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.util.StringUtils;
 
-import com.google.protobuf.Descriptors;
-import com.google.protobuf.Message;
-import com.google.protobuf.Service;
-import com.google.protobuf.ServiceException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
 
 /**
  * HTable interface to remote tables accessed via REST gateway
@@ -851,4 +850,9 @@ 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 rm) throws IOException {
+    throw new UnsupportedOperationException("checkAndMutate not implemented");
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/dab2af79/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/dab2af79/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 093eb0b..2e63f1a 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
@@ -1509,7 +1509,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);