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}