diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/CheckAndRowMutate.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/CheckAndRowMutate.java new file mode 100644 index 000000000000..4207d10ccaa8 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/CheckAndRowMutate.java @@ -0,0 +1,170 @@ +/* + * 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 java.io.IOException; +import java.util.Arrays; +import org.apache.hadoop.hbase.CompareOperator; +import org.apache.hadoop.hbase.io.TimeRange; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; + +/** + * Used to perform CheckAndRowMutate operations on a single row. + */ +@InterfaceAudience.Public +public class CheckAndRowMutate implements Row { + private final byte[] row; + private final byte[] family; + private byte[] qualifier; + private TimeRange timeRange = null; + private CompareOperator op; + private byte[] value; + private Mutation mutation = null; + + /** + * Create a CheckAndRowMutate operation for the specified row. + * + * @param row row key + * @param family family + */ + public CheckAndRowMutate(byte[] row, byte[] family) { + this.row = Bytes.copy(Mutation.checkRow(row)); + this.family = Preconditions.checkNotNull(family, "family is null"); + } + + /** + * Create a CheckAndRowMutate operation for the specified row, + * and an existing row lock. + * + * @param row row key + * @param family family + * @param qualifier qualifier + * @param value value + * @param mutation mutation + */ + public CheckAndRowMutate(byte[] row, byte[] family, byte[] qualifier, CompareOperator compareOp, + byte[] value, Mutation mutation) { + this.row = Bytes.copy(Mutation.checkRow(row)); + this.family = Preconditions.checkNotNull(family, "family is null"); + this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null"); + this.op = Preconditions.checkNotNull(compareOp, "compareOp is null"); + this.value = Preconditions.checkNotNull(value, "value is null"); + this.mutation = mutation; + } + + public CheckAndRowMutate qualifier(byte[] qualifier) { + this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null. Consider using" + + " an empty byte array, or just do not call this method if you want a null qualifier"); + return this; + } + + public CheckAndRowMutate timeRange(TimeRange timeRange) { + this.timeRange = timeRange; + return this; + } + + public CheckAndRowMutate ifNotExists() { + this.op = CompareOperator.EQUAL; + this.value = null; + return this; + } + + public CheckAndRowMutate ifMatches(CompareOperator compareOp, byte[] value) { + this.op = Preconditions.checkNotNull(compareOp, "compareOp is null"); + this.value = Preconditions.checkNotNull(value, "value is null"); + return this; + } + + public CheckAndRowMutate addMutation(Mutation mutation) throws IOException { + this.mutation = mutation; + return this; + } + + /** + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0. + * Use {@link Row#COMPARATOR} instead + */ + @Deprecated + @Override + public int compareTo(Row i) { + return Bytes.compareTo(this.getRow(), i.getRow()); + } + + /** + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0. + * No replacement + */ + @Deprecated + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CheckAndRowMutate) { + CheckAndRowMutate other = (CheckAndRowMutate)obj; + return compareTo(other) == 0; + } + return false; + } + + /** + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0. + * No replacement + */ + @Deprecated + @Override + public int hashCode(){ + return Arrays.hashCode(row); + } + + /** + * Method for retrieving the delete's row + * + * @return row + */ + @Override + public byte[] getRow() { + return this.row; + } + + public byte[] getFamily() { + return family; + } + + public byte[] getQualifier() { + return qualifier; + } + + public TimeRange getTimeRange() { + return timeRange; + } + + public CompareOperator getOp() { + return op; + } + + public byte[] getValue() { + return value; + } + + public Mutation getMutation() { + return mutation; + } +} 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 54ab606c37c7..e28ba6a2068d 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 @@ -1192,6 +1192,25 @@ public void batchCoprocessorService( } } + @Override + public Boolean [] checkAndRowMutate(final List checkAndRowMutates) + throws IOException { + try { + Object[] r1 = new Object[checkAndRowMutates.size()]; + batch((List)checkAndRowMutates, r1, readRpcTimeoutMs); + // Translate. + Boolean [] results = new Boolean[r1.length]; + int i = 0; + for (Object obj: r1) { + // Batch ensures if there is a failure we get an exception instead + results[i++] = ((Result)obj).getExists(); + } + return results; + } catch (InterruptedException e) { + throw (InterruptedIOException)new InterruptedIOException().initCause(e); + } + } + @Override public RegionLocator getRegionLocator() { return this.locator; 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 a6c10ea12f49..67dcb284443f 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 @@ -479,6 +479,11 @@ default boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, return checkAndMutate(row, family, qualifier, op, value, mutations); } + default Boolean[] checkAndRowMutate(final List checkAndRowMutates) + throws IOException { + throw new NotImplementedException("Add an implementation!"); + } + /** * Atomically checks if a row/family/qualifier value matches the expected value. If it does, it * adds the Put/Delete/RowMutations. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java index 0a2063de7298..47885e795199 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java @@ -65,6 +65,7 @@ import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Append; +import org.apache.hadoop.hbase.client.CheckAndRowMutate; import org.apache.hadoop.hbase.client.ClientUtil; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; @@ -90,6 +91,7 @@ import org.apache.hadoop.hbase.client.metrics.ScanMetrics; import org.apache.hadoop.hbase.client.security.SecurityCapability; import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.filter.BinaryComparator; import org.apache.hadoop.hbase.filter.ByteArrayComparable; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.io.TimeRange; @@ -3357,4 +3359,30 @@ public static RegionStatesCount toTableRegionStatesCount( .build(); } + public static ClientProtos.CheckAndRowMutate toCheckAndRowMutate( + final CheckAndRowMutate checkAndRowMutate) throws IOException { + ClientProtos.CheckAndRowMutate.Builder builder = + ClientProtos.CheckAndRowMutate.newBuilder(); + builder.setRow(UnsafeByteOperations.unsafeWrap(checkAndRowMutate.getRow())) + .setFamily(UnsafeByteOperations.unsafeWrap(checkAndRowMutate.getFamily())) + .setQualifier(UnsafeByteOperations.unsafeWrap(checkAndRowMutate.getQualifier() == null ? + HConstants.EMPTY_BYTE_ARRAY : checkAndRowMutate.getQualifier())) + .setComparator(ProtobufUtil.toComparator( + new BinaryComparator(checkAndRowMutate.getValue()))) + .setCompareType(HBaseProtos.CompareType.valueOf(checkAndRowMutate.getOp().name())) + .setTimeRange(ProtobufUtil.toTimeRange(checkAndRowMutate.getTimeRange())); + MutationProto.Builder mutationBuilder = MutationProto.newBuilder(); + MutationType mutateType = null; + Mutation mutation = checkAndRowMutate.getMutation(); + if (mutation instanceof Put) { + mutateType = MutationType.PUT; + } else if (mutation instanceof Delete) { + mutateType = MutationType.DELETE; + } else { + throw new DoNotRetryIOException(mutation.getClass().getName() + " is not support"); + } + builder.setMutation(ProtobufUtil.toMutation(mutateType, mutation, mutationBuilder)); + return builder.build(); + } + } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java index 7dc8645d2ac5..f8a1c677ce85 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java @@ -37,6 +37,7 @@ import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Action; import org.apache.hadoop.hbase.client.Append; +import org.apache.hadoop.hbase.client.CheckAndRowMutate; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Durability; @@ -694,6 +695,9 @@ public static void buildRegionActions(final byte[] regionName, .setRequest(value))); } else if (row instanceof RowMutations) { rowMutationsList.add(action); + } else if (row instanceof CheckAndRowMutate) { + builder.addAction(actionBuilder.setCheckAndRowMutate( + ProtobufUtil.toCheckAndRowMutate((CheckAndRowMutate)row))); } else { throw new DoNotRetryIOException("Multi doesn't support " + row.getClass().getName()); } @@ -817,6 +821,9 @@ public static void buildNoDataRegionActions(final byte[] regionName, .setRequest(value))); } else if (row instanceof RowMutations) { rowMutationsList.add(action); + } else if (row instanceof CheckAndRowMutate) { + builder.addAction(actionBuilder.setCheckAndRowMutate( + ProtobufUtil.toCheckAndRowMutate((CheckAndRowMutate)row))); } else { throw new DoNotRetryIOException("Multi doesn't support " + row.getClass().getName()); } diff --git a/hbase-protocol-shaded/src/main/protobuf/Client.proto b/hbase-protocol-shaded/src/main/protobuf/Client.proto index 07d8d711a0a2..226462e1c641 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Client.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Client.proto @@ -92,6 +92,16 @@ message Get { optional bool load_column_families_on_demand = 14; /* DO NOT add defaults to load_column_families_on_demand. */ } +message CheckAndRowMutate { + required bytes row = 1; + required bytes family = 2; + required bytes qualifier = 3; + required CompareType compare_type = 4; + required Comparator comparator = 5; + optional TimeRange time_range = 6; + required MutationProto mutation = 7; +} + message Result { // Result includes the Cells or else it just has a count of Cells // that are carried otherwise. @@ -443,6 +453,7 @@ message Action { optional MutationProto mutation = 2; optional Get get = 3; optional CoprocessorServiceCall service_call = 4; + optional CheckAndRowMutate checkAndRowMutate = 5; } /** 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 00e616915be1..b19c3014c87b 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 @@ -894,6 +894,23 @@ private List doNonAtomicRegionMutation(final HRegion region, default: throw new DoNotRetryIOException("Unsupported mutate type: " + type.name()); } + } else if (action.hasCheckAndRowMutate()) { + ClientProtos.CheckAndRowMutate checkAndRowMutate = action.getCheckAndRowMutate(); + byte[] row = checkAndRowMutate.getRow().toByteArray(); + byte[] family = checkAndRowMutate.getFamily().toByteArray(); + byte[] qualifier = checkAndRowMutate.getQualifier().toByteArray(); + CompareOperator compareOp = + CompareOperator.valueOf(checkAndRowMutate.getCompareType().name()); + ByteArrayComparable comparator = + ProtobufUtil.toComparator(checkAndRowMutate.getComparator()); + TimeRange timeRange = checkAndRowMutate.hasTimeRange() ? + ProtobufUtil.toTimeRange(checkAndRowMutate.getTimeRange()) : + TimeRange.allTime(); + Mutation mutation = ProtobufUtil.toMutation(checkAndRowMutate.getMutation()); + boolean result = region.checkAndMutate(row, family, + qualifier, compareOp, comparator, timeRange, mutation); + r = new Result(); + r.setExists(result); } else { throw new HBaseIOException("Unexpected Action type"); } 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 index 15ef065d4737..60a478c05b8a 100644 --- 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 @@ -19,7 +19,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - import java.io.IOException; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseTestingUtility; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndRowMutate.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndRowMutate.java new file mode 100644 index 000000000000..c69f777ec33f --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestCheckAndRowMutate.java @@ -0,0 +1,257 @@ +/** + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.hbase.CompareOperator; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +@Category(MediumTests.class) +public class TestCheckAndRowMutate { + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestCheckAndRowMutate.class); + + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final byte[] ROWKEY = Bytes.toBytes("12345"); + private static final byte[] FAMILY = Bytes.toBytes("cf"); + + @Rule + public TestName name = new TestName(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + TEST_UTIL.startMiniCluster(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + private Table createTable() throws IOException, InterruptedException { + final TableName tableName = TableName.valueOf(name.getMethodName()); + Table table = TEST_UTIL.createTable(tableName, FAMILY); + TEST_UTIL.waitTableAvailable(tableName.getName(), 5000); + return table; + } + + private void putOneRow(Table table) throws IOException { + Put put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("A"), Bytes.toBytes("a")); + put.addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("b")); + put.addColumn(FAMILY, Bytes.toBytes("C"), Bytes.toBytes("c")); + table.put(put); + } + + private void getOneRowAndAssertAllExist(final Table table) throws IOException { + Get get = new Get(ROWKEY); + Result result = table.get(get); + assertEquals("Column A value should be a", "a", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("A")))); + assertEquals("Column B value should be b", "b", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B")))); + assertEquals("Column C value should be c", "c", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("C")))); + } + + private void deleteOneRow(Table table) throws IOException { + Delete delete = new Delete(ROWKEY); + table.delete(delete); + } + + private List makeCheckAndRowMutatesWithPut() throws IOException { + List checkAndRowMutates = new ArrayList<>(); + Put put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("A"), Bytes.toBytes("aa")); + CheckAndRowMutate checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("A")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("a")) + .addMutation(put); + checkAndRowMutates.add(checkAndRowMutate); + + put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("bb")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("B")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("b")) + .addMutation(put); + checkAndRowMutates.add(checkAndRowMutate); + + put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("C"), Bytes.toBytes("cc")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("C")) + .ifMatches(CompareOperator.GREATER, Bytes.toBytes("c")) + .addMutation(put); + checkAndRowMutates.add(checkAndRowMutate); + return checkAndRowMutates; + } + + private void getOneRowAndAssertCNotChanged(final Table table) throws IOException { + Get get = new Get(ROWKEY); + Result result = table.get(get); + assertEquals("Column A value should be aa", "aa", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("A")))); + assertEquals("Column B value should be bb", "bb", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B")))); + assertEquals("Column B value should be c", "c", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("C")))); + } + + private List makeCheckAndRowMutatesWithDelete() throws IOException { + List checkAndRowMutates = new ArrayList<>(); + Delete delete = new Delete(ROWKEY); + delete.addColumns(FAMILY, Bytes.toBytes("A")); + CheckAndRowMutate checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("A")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("a")) + .addMutation(delete); + checkAndRowMutates.add(checkAndRowMutate); + + delete = new Delete(ROWKEY); + delete.addColumns(FAMILY, Bytes.toBytes("B")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("B")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("b")) + .addMutation(delete); + checkAndRowMutates.add(checkAndRowMutate); + + delete = new Delete(ROWKEY); + delete.addColumns(FAMILY, Bytes.toBytes("C")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("C")) + .ifMatches(CompareOperator.GREATER, Bytes.toBytes("c")) + .addMutation(delete); + checkAndRowMutates.add(checkAndRowMutate); + return checkAndRowMutates; + } + + private void getOneRowAndAssertCNotDelete(final Table table) throws IOException { + Get get = new Get(ROWKEY); + Result result = table.get(get); + assertNull("Column A should not exist", result.getValue(FAMILY, Bytes.toBytes("A"))); + assertNull("Column B should not exist", result.getValue(FAMILY, Bytes.toBytes("B"))); + assertEquals("Column C value should be c", "c", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("C")))); + } + + private List makeCheckAndRowMutatesWithPutAndDelete() throws IOException { + List checkAndRowMutates = new ArrayList<>(); + Put put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("A"), Bytes.toBytes("aa")); + CheckAndRowMutate checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("A")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("a")) + .addMutation(put); + checkAndRowMutates.add(checkAndRowMutate); + + put = new Put(ROWKEY); + put.addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("bb")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("B")) + .ifMatches(CompareOperator.GREATER, Bytes.toBytes("b")) + .addMutation(put); + checkAndRowMutates.add(checkAndRowMutate); + + Delete delete = new Delete(ROWKEY); + delete.addColumns(FAMILY, Bytes.toBytes("C")); + checkAndRowMutate = new CheckAndRowMutate(ROWKEY, FAMILY) + .qualifier(Bytes.toBytes("C")) + .ifMatches(CompareOperator.EQUAL, Bytes.toBytes("c")) + .addMutation(delete); + checkAndRowMutates.add(checkAndRowMutate); + return checkAndRowMutates; + } + + private void getOneRowAndAssertAChangedAndCDelete(final Table table) throws IOException { + Get get = new Get(ROWKEY); + Result result = table.get(get); + assertEquals("Column A value should be aa", "aa", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("A")))); + assertEquals("Column B value should be bb", "b", + Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B")))); + assertNull("Column C should not exist", result.getValue(FAMILY, Bytes.toBytes("C"))); + } + + @Test + public void testCheckAndRowMutateWithPut() throws Throwable { + try (Table table = createTable()) { + // put one row + putOneRow(table); + // get row back and assert the values + getOneRowAndAssertAllExist(table); + + // put the same row again with C column not changed + List carm = makeCheckAndRowMutatesWithPut(); + Boolean[] result = table.checkAndRowMutate(carm); + assertTrue(result[0]); + assertTrue(result[1]); + assertFalse(result[2]); + getOneRowAndAssertCNotChanged(table); + + // delete one row + deleteOneRow(table); + // put one row + putOneRow(table); + // get row back and assert the values + getOneRowAndAssertAllExist(table); + + // put the same row again with C column not deleted + carm = makeCheckAndRowMutatesWithDelete(); + result = table.checkAndRowMutate(carm); + assertTrue(result[0]); + assertTrue(result[1]); + assertFalse(result[2]); + getOneRowAndAssertCNotDelete(table); + + // delete one row + deleteOneRow(table); + // put one row + putOneRow(table); + // get row back and assert the values + getOneRowAndAssertAllExist(table); + + // put the same row again with A column changed and C column deleted + carm = makeCheckAndRowMutatesWithPutAndDelete(); + result = table.checkAndRowMutate(carm); + assertTrue(result[0]); + assertFalse(result[1]); + assertTrue(result[2]); + getOneRowAndAssertAChangedAndCDelete(table); + } + } +}