001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.client; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.IOException; 026import java.util.Collections; 027import org.apache.hadoop.hbase.CompareOperator; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.filter.BinaryComparator; 032import org.apache.hadoop.hbase.filter.FamilyFilter; 033import org.apache.hadoop.hbase.filter.FilterList; 034import org.apache.hadoop.hbase.filter.QualifierFilter; 035import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; 036import org.apache.hadoop.hbase.filter.TimestampsFilter; 037import org.apache.hadoop.hbase.io.TimeRange; 038import org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException; 039import org.apache.hadoop.hbase.testclassification.MediumTests; 040import org.apache.hadoop.hbase.util.Bytes; 041import org.junit.AfterClass; 042import org.junit.BeforeClass; 043import org.junit.ClassRule; 044import org.junit.Rule; 045import org.junit.Test; 046import org.junit.experimental.categories.Category; 047import org.junit.rules.TestName; 048 049@Category(MediumTests.class) 050public class TestCheckAndMutate { 051 052 @ClassRule 053 public static final HBaseClassTestRule CLASS_RULE = 054 HBaseClassTestRule.forClass(TestCheckAndMutate.class); 055 056 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 057 private static final byte[] ROWKEY = Bytes.toBytes("12345"); 058 private static final byte[] FAMILY = Bytes.toBytes("cf"); 059 060 @Rule 061 public TestName name = new TestName(); 062 063 @BeforeClass 064 public static void setUpBeforeClass() throws Exception { 065 TEST_UTIL.startMiniCluster(); 066 } 067 068 @AfterClass 069 public static void tearDownAfterClass() throws Exception { 070 TEST_UTIL.shutdownMiniCluster(); 071 } 072 073 private Table createTable() 074 throws IOException, InterruptedException { 075 final TableName tableName = TableName.valueOf(name.getMethodName()); 076 Table table = TEST_UTIL.createTable(tableName, FAMILY); 077 TEST_UTIL.waitTableAvailable(tableName.getName(), 5000); 078 return table; 079 } 080 081 private void putOneRow(Table table) throws IOException { 082 Put put = new Put(ROWKEY); 083 put.addColumn(FAMILY, Bytes.toBytes("A"), Bytes.toBytes("a")); 084 put.addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("b")); 085 put.addColumn(FAMILY, Bytes.toBytes("C"), Bytes.toBytes("c")); 086 table.put(put); 087 } 088 089 private void getOneRowAndAssertAllExist(final Table table) throws IOException { 090 Get get = new Get(ROWKEY); 091 Result result = table.get(get); 092 assertTrue("Column A value should be a", 093 Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("A"))).equals("a")); 094 assertTrue("Column B value should be b", 095 Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B"))).equals("b")); 096 assertTrue("Column C value should be c", 097 Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("C"))).equals("c")); 098 } 099 100 private void getOneRowAndAssertAllButCExist(final Table table) throws IOException { 101 Get get = new Get(ROWKEY); 102 Result result = table.get(get); 103 assertTrue("Column A value should be a", 104 Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("A"))).equals("a")); 105 assertTrue("Column B value should be b", 106 Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B"))).equals("b")); 107 assertTrue("Column C should not exist", 108 result.getValue(FAMILY, Bytes.toBytes("C")) == null); 109 } 110 111 private RowMutations makeRowMutationsWithColumnCDeleted() throws IOException { 112 RowMutations rm = new RowMutations(ROWKEY, 2); 113 Put put = new Put(ROWKEY); 114 put.addColumn(FAMILY, Bytes.toBytes("A"), Bytes.toBytes("a")); 115 put.addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("b")); 116 rm.add(put); 117 Delete del = new Delete(ROWKEY); 118 del.addColumn(FAMILY, Bytes.toBytes("C")); 119 rm.add(del); 120 return rm; 121 } 122 123 private RowMutations getBogusRowMutations() throws IOException { 124 Put p = new Put(ROWKEY); 125 byte[] value = new byte[0]; 126 p.addColumn(new byte[]{'b', 'o', 'g', 'u', 's'}, new byte[]{'A'}, value); 127 RowMutations rm = new RowMutations(ROWKEY); 128 rm.add(p); 129 return rm; 130 } 131 132 @Test 133 public void testCheckAndMutate() throws Throwable { 134 try (Table table = createTable()) { 135 // put one row 136 putOneRow(table); 137 // get row back and assert the values 138 getOneRowAndAssertAllExist(table); 139 140 // put the same row again with C column deleted 141 RowMutations rm = makeRowMutationsWithColumnCDeleted(); 142 boolean res = table.checkAndMutate(ROWKEY, FAMILY).qualifier(Bytes.toBytes("A")) 143 .ifEquals(Bytes.toBytes("a")).thenMutate(rm); 144 assertTrue(res); 145 146 // get row back and assert the values 147 getOneRowAndAssertAllButCExist(table); 148 149 //Test that we get a region level exception 150 try { 151 rm = getBogusRowMutations(); 152 table.checkAndMutate(ROWKEY, FAMILY).qualifier(Bytes.toBytes("A")) 153 .ifEquals(Bytes.toBytes("a")).thenMutate(rm); 154 fail("Expected NoSuchColumnFamilyException"); 155 } catch (RetriesExhaustedWithDetailsException e) { 156 try { 157 throw e.getCause(0); 158 } catch (NoSuchColumnFamilyException e1) { 159 // expected 160 } 161 } 162 } 163 } 164 165 @Test 166 public void testCheckAndMutateWithBuilder() throws Throwable { 167 try (Table table = createTable()) { 168 // put one row 169 putOneRow(table); 170 // get row back and assert the values 171 getOneRowAndAssertAllExist(table); 172 173 // put the same row again with C column deleted 174 RowMutations rm = makeRowMutationsWithColumnCDeleted(); 175 boolean res = table.checkAndMutate(ROWKEY, FAMILY).qualifier(Bytes.toBytes("A")) 176 .ifEquals(Bytes.toBytes("a")).thenMutate(rm); 177 assertTrue(res); 178 179 // get row back and assert the values 180 getOneRowAndAssertAllButCExist(table); 181 182 //Test that we get a region level exception 183 try { 184 rm = getBogusRowMutations(); 185 table.checkAndMutate(ROWKEY, FAMILY).qualifier(Bytes.toBytes("A")) 186 .ifEquals(Bytes.toBytes("a")).thenMutate(rm); 187 fail("Expected NoSuchColumnFamilyException"); 188 } catch (RetriesExhaustedWithDetailsException e) { 189 try { 190 throw e.getCause(0); 191 } catch (NoSuchColumnFamilyException e1) { 192 // expected 193 } 194 } 195 } 196 } 197 198 @Test 199 public void testCheckAndMutateWithSingleFilter() throws Throwable { 200 try (Table table = createTable()) { 201 // put one row 202 putOneRow(table); 203 // get row back and assert the values 204 getOneRowAndAssertAllExist(table); 205 206 // Put with success 207 boolean ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, 208 Bytes.toBytes("A"), CompareOperator.EQUAL, Bytes.toBytes("a"))) 209 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"), Bytes.toBytes("d"))); 210 assertTrue(ok); 211 212 Result result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"))); 213 assertEquals("d", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("D")))); 214 215 // Put with failure 216 ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), 217 CompareOperator.EQUAL, Bytes.toBytes("b"))) 218 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("E"), Bytes.toBytes("e"))); 219 assertFalse(ok); 220 221 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("E")))); 222 223 // Delete with success 224 ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), 225 CompareOperator.EQUAL, Bytes.toBytes("a"))) 226 .thenDelete(new Delete(ROWKEY).addColumns(FAMILY, Bytes.toBytes("D"))); 227 assertTrue(ok); 228 229 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D")))); 230 231 // Mutate with success 232 ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, Bytes.toBytes("B"), 233 CompareOperator.EQUAL, Bytes.toBytes("b"))) 234 .thenMutate(new RowMutations(ROWKEY) 235 .add((Mutation) new Put(ROWKEY) 236 .addColumn(FAMILY, Bytes.toBytes("D"), Bytes.toBytes("d"))) 237 .add((Mutation) new Delete(ROWKEY).addColumns(FAMILY, Bytes.toBytes("A")))); 238 assertTrue(ok); 239 240 result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"))); 241 assertEquals("d", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("D")))); 242 243 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("A")))); 244 } 245 } 246 247 @Test 248 public void testCheckAndMutateWithMultipleFilters() throws Throwable { 249 try (Table table = createTable()) { 250 // put one row 251 putOneRow(table); 252 // get row back and assert the values 253 getOneRowAndAssertAllExist(table); 254 255 // Put with success 256 boolean ok = table.checkAndMutate(ROWKEY, new FilterList( 257 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), CompareOperator.EQUAL, 258 Bytes.toBytes("a")), 259 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("B"), CompareOperator.EQUAL, 260 Bytes.toBytes("b")) 261 )) 262 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"), Bytes.toBytes("d"))); 263 assertTrue(ok); 264 265 Result result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"))); 266 assertEquals("d", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("D")))); 267 268 // Put with failure 269 ok = table.checkAndMutate(ROWKEY, new FilterList( 270 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), CompareOperator.EQUAL, 271 Bytes.toBytes("a")), 272 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("B"), CompareOperator.EQUAL, 273 Bytes.toBytes("c")) 274 )) 275 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("E"), Bytes.toBytes("e"))); 276 assertFalse(ok); 277 278 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("E")))); 279 280 // Delete with success 281 ok = table.checkAndMutate(ROWKEY, new FilterList( 282 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), CompareOperator.EQUAL, 283 Bytes.toBytes("a")), 284 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("B"), CompareOperator.EQUAL, 285 Bytes.toBytes("b")) 286 )) 287 .thenDelete(new Delete(ROWKEY).addColumns(FAMILY, Bytes.toBytes("D"))); 288 assertTrue(ok); 289 290 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D")))); 291 292 // Mutate with success 293 ok = table.checkAndMutate(ROWKEY, new FilterList( 294 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), CompareOperator.EQUAL, 295 Bytes.toBytes("a")), 296 new SingleColumnValueFilter(FAMILY, Bytes.toBytes("B"), CompareOperator.EQUAL, 297 Bytes.toBytes("b")) 298 )) 299 .thenMutate(new RowMutations(ROWKEY) 300 .add((Mutation) new Put(ROWKEY) 301 .addColumn(FAMILY, Bytes.toBytes("D"), Bytes.toBytes("d"))) 302 .add((Mutation) new Delete(ROWKEY).addColumns(FAMILY, Bytes.toBytes("A")))); 303 assertTrue(ok); 304 305 result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"))); 306 assertEquals("d", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("D")))); 307 308 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("A")))); 309 } 310 } 311 312 @Test 313 public void testCheckAndMutateWithTimestampFilter() throws Throwable { 314 try (Table table = createTable()) { 315 // Put with specifying the timestamp 316 table.put(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("A"), 100, Bytes.toBytes("a"))); 317 318 // Put with success 319 boolean ok = table.checkAndMutate(ROWKEY, new FilterList( 320 new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(FAMILY)), 321 new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("A"))), 322 new TimestampsFilter(Collections.singletonList(100L)) 323 )) 324 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("b"))); 325 assertTrue(ok); 326 327 Result result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("B"))); 328 assertEquals("b", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B")))); 329 330 // Put with failure 331 ok = table.checkAndMutate(ROWKEY, new FilterList( 332 new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(FAMILY)), 333 new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("A"))), 334 new TimestampsFilter(Collections.singletonList(101L)) 335 )) 336 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("C"), Bytes.toBytes("c"))); 337 assertFalse(ok); 338 339 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("C")))); 340 } 341 } 342 343 @Test 344 public void testCheckAndMutateWithFilterAndTimeRange() throws Throwable { 345 try (Table table = createTable()) { 346 // Put with specifying the timestamp 347 table.put(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("A"), 100, Bytes.toBytes("a"))); 348 349 // Put with success 350 boolean ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, 351 Bytes.toBytes("A"), CompareOperator.EQUAL, Bytes.toBytes("a"))) 352 .timeRange(TimeRange.between(0, 101)) 353 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("B"), Bytes.toBytes("b"))); 354 assertTrue(ok); 355 356 Result result = table.get(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("B"))); 357 assertEquals("b", Bytes.toString(result.getValue(FAMILY, Bytes.toBytes("B")))); 358 359 // Put with failure 360 ok = table.checkAndMutate(ROWKEY, new SingleColumnValueFilter(FAMILY, Bytes.toBytes("A"), 361 CompareOperator.EQUAL, Bytes.toBytes("a"))) 362 .timeRange(TimeRange.between(0, 100)) 363 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("C"), Bytes.toBytes("c"))); 364 assertFalse(ok); 365 366 assertFalse(table.exists(new Get(ROWKEY).addColumn(FAMILY, Bytes.toBytes("C")))); 367 } 368 } 369 370 @Test(expected = NullPointerException.class) 371 public void testCheckAndMutateWithNotSpecifyingCondition() throws Throwable { 372 try (Table table = createTable()) { 373 table.checkAndMutate(ROWKEY, FAMILY) 374 .thenPut(new Put(ROWKEY).addColumn(FAMILY, Bytes.toBytes("D"), Bytes.toBytes("d"))); 375 } 376 } 377}