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.*;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.List;
026import org.apache.hadoop.hbase.*;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.filter.Filter;
029import org.apache.hadoop.hbase.filter.TimestampsFilter;
030import org.apache.hadoop.hbase.testclassification.ClientTests;
031import org.apache.hadoop.hbase.testclassification.MediumTests;
032import org.apache.hadoop.hbase.util.Bytes;
033import org.junit.After;
034import org.junit.AfterClass;
035import org.junit.Before;
036import org.junit.BeforeClass;
037import org.junit.ClassRule;
038import org.junit.Rule;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041import org.junit.rules.TestName;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * Run tests related to {@link TimestampsFilter} using HBase client APIs.
047 * Sets up the HBase mini cluster once at start. Each creates a table
048 * named for the method and does its stuff against that.
049 */
050@Category({MediumTests.class, ClientTests.class})
051public class TestTimestampsFilter {
052
053  @ClassRule
054  public static final HBaseClassTestRule CLASS_RULE =
055      HBaseClassTestRule.forClass(TestTimestampsFilter.class);
056
057  private static final Logger LOG = LoggerFactory.getLogger(TestTimestampsFilter.class);
058  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
059
060  @Rule
061  public TestName name = new TestName();
062
063  /**
064   * @throws java.lang.Exception
065   */
066  @BeforeClass
067  public static void setUpBeforeClass() throws Exception {
068    TEST_UTIL.startMiniCluster();
069  }
070
071  /**
072   * @throws java.lang.Exception
073   */
074  @AfterClass
075  public static void tearDownAfterClass() throws Exception {
076    TEST_UTIL.shutdownMiniCluster();
077  }
078
079  /**
080   * @throws java.lang.Exception
081   */
082  @Before
083  public void setUp() throws Exception {
084    // Nothing to do.
085  }
086
087  /**
088   * @throws java.lang.Exception
089   */
090  @After
091  public void tearDown() throws Exception {
092    // Nothing to do.
093  }
094
095  /**
096   * Test from client side for TimestampsFilter.
097   *
098   * The TimestampsFilter provides the ability to request cells (KeyValues)
099   * whose timestamp/version is in the specified list of timestamps/version.
100   *
101   * @throws Exception
102   */
103  @Test
104  public void testTimestampsFilter() throws Exception {
105    final byte [] TABLE = Bytes.toBytes(name.getMethodName());
106    byte [] FAMILY = Bytes.toBytes("event_log");
107    byte [][] FAMILIES = new byte[][] { FAMILY };
108    Cell kvs[];
109
110    // create table; set versions to max...
111    Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE);
112
113    for (int rowIdx = 0; rowIdx < 5; rowIdx++) {
114      for (int colIdx = 0; colIdx < 5; colIdx++) {
115        // insert versions 201..300
116        putNVersions(ht, FAMILY, rowIdx, colIdx, 201, 300);
117        // insert versions 1..100
118        putNVersions(ht, FAMILY, rowIdx, colIdx, 1, 100);
119      }
120    }
121
122    // do some verification before flush
123    verifyInsertedValues(ht, FAMILY);
124
125    TEST_UTIL.flush();
126
127    // do some verification after flush
128    verifyInsertedValues(ht, FAMILY);
129
130    // Insert some more versions after flush. These should be in memstore.
131    // After this we should have data in both memstore & HFiles.
132    for (int rowIdx = 0; rowIdx < 5; rowIdx++) {
133      for (int colIdx = 0; colIdx < 5; colIdx++) {
134        putNVersions(ht, FAMILY, rowIdx, colIdx, 301, 400);
135        putNVersions(ht, FAMILY, rowIdx, colIdx, 101, 200);
136      }
137    }
138
139    for (int rowIdx = 0; rowIdx < 5; rowIdx++) {
140      for (int colIdx = 0; colIdx < 5; colIdx++) {
141        kvs = getNVersions(ht, FAMILY, rowIdx, colIdx,
142                           Arrays.asList(505L, 5L, 105L, 305L, 205L));
143        assertEquals(4, kvs.length);
144        checkOneCell(kvs[0], FAMILY, rowIdx, colIdx, 305);
145        checkOneCell(kvs[1], FAMILY, rowIdx, colIdx, 205);
146        checkOneCell(kvs[2], FAMILY, rowIdx, colIdx, 105);
147        checkOneCell(kvs[3], FAMILY, rowIdx, colIdx, 5);
148      }
149    }
150
151    // Request an empty list of versions using the Timestamps filter;
152    // Should return none.
153    kvs = getNVersions(ht, FAMILY, 2, 2, new ArrayList<>());
154    assertEquals(0, kvs == null? 0: kvs.length);
155
156    //
157    // Test the filter using a Scan operation
158    // Scan rows 0..4. For each row, get all its columns, but only
159    // those versions of the columns with the specified timestamps.
160    Result[] results = scanNVersions(ht, FAMILY, 0, 4,
161                                     Arrays.asList(6L, 106L, 306L));
162    assertEquals("# of rows returned from scan", 5, results.length);
163    for (int rowIdx = 0; rowIdx < 5; rowIdx++) {
164      kvs = results[rowIdx].rawCells();
165      // each row should have 5 columns.
166      // And we have requested 3 versions for each.
167      assertEquals("Number of KeyValues in result for row:" + rowIdx,
168                   3*5, kvs.length);
169      for (int colIdx = 0; colIdx < 5; colIdx++) {
170        int offset = colIdx * 3;
171        checkOneCell(kvs[offset + 0], FAMILY, rowIdx, colIdx, 306);
172        checkOneCell(kvs[offset + 1], FAMILY, rowIdx, colIdx, 106);
173        checkOneCell(kvs[offset + 2], FAMILY, rowIdx, colIdx, 6);
174      }
175    }
176    ht.close();
177  }
178
179  @Test
180  public void testMultiColumns() throws Exception {
181    final byte [] TABLE = Bytes.toBytes(name.getMethodName());
182    byte [] FAMILY = Bytes.toBytes("event_log");
183    byte [][] FAMILIES = new byte[][] { FAMILY };
184
185    // create table; set versions to max...
186    Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE);
187
188    Put p = new Put(Bytes.toBytes("row"));
189    p.addColumn(FAMILY, Bytes.toBytes("column0"), 3L, Bytes.toBytes("value0-3"));
190    p.addColumn(FAMILY, Bytes.toBytes("column1"), 3L, Bytes.toBytes("value1-3"));
191    p.addColumn(FAMILY, Bytes.toBytes("column2"), 1L, Bytes.toBytes("value2-1"));
192    p.addColumn(FAMILY, Bytes.toBytes("column2"), 2L, Bytes.toBytes("value2-2"));
193    p.addColumn(FAMILY, Bytes.toBytes("column2"), 3L, Bytes.toBytes("value2-3"));
194    p.addColumn(FAMILY, Bytes.toBytes("column3"), 2L, Bytes.toBytes("value3-2"));
195    p.addColumn(FAMILY, Bytes.toBytes("column4"), 1L, Bytes.toBytes("value4-1"));
196    p.addColumn(FAMILY, Bytes.toBytes("column4"), 2L, Bytes.toBytes("value4-2"));
197    p.addColumn(FAMILY, Bytes.toBytes("column4"), 3L, Bytes.toBytes("value4-3"));
198    ht.put(p);
199
200    ArrayList<Long> timestamps = new ArrayList<>();
201    timestamps.add(new Long(3));
202    TimestampsFilter filter = new TimestampsFilter(timestamps);
203
204    Get g = new Get(Bytes.toBytes("row"));
205    g.setFilter(filter);
206    g.setMaxVersions();
207    g.addColumn(FAMILY, Bytes.toBytes("column2"));
208    g.addColumn(FAMILY, Bytes.toBytes("column4"));
209
210    Result result = ht.get(g);
211    for (Cell kv : result.listCells()) {
212      System.out.println("found row " + Bytes.toString(CellUtil.cloneRow(kv)) +
213          ", column " + Bytes.toString(CellUtil.cloneQualifier(kv)) + ", value "
214          + Bytes.toString(CellUtil.cloneValue(kv)));
215    }
216
217    assertEquals(2, result.listCells().size());
218    assertTrue(CellUtil.matchingValue(result.listCells().get(0), Bytes.toBytes("value2-3")));
219    assertTrue(CellUtil.matchingValue(result.listCells().get(1), Bytes.toBytes("value4-3")));
220
221    ht.close();
222  }
223
224  /**
225   * Test TimestampsFilter in the presence of version deletes.
226   *
227   * @throws Exception
228   */
229  @Test
230  public void testWithVersionDeletes() throws Exception {
231
232    // first test from memstore (without flushing).
233    testWithVersionDeletes(false);
234
235    // run same test against HFiles (by forcing a flush).
236    testWithVersionDeletes(true);
237  }
238
239  private void testWithVersionDeletes(boolean flushTables) throws IOException {
240    final byte [] TABLE = Bytes.toBytes(name.getMethodName() + "_" +
241                                   (flushTables ? "flush" : "noflush"));
242    byte [] FAMILY = Bytes.toBytes("event_log");
243    byte [][] FAMILIES = new byte[][] { FAMILY };
244
245    // create table; set versions to max...
246    Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE);
247
248    // For row:0, col:0: insert versions 1 through 5.
249    putNVersions(ht, FAMILY, 0, 0, 1, 5);
250
251    // delete version 4.
252    deleteOneVersion(ht, FAMILY, 0, 0, 4);
253
254    if (flushTables) {
255      TEST_UTIL.flush();
256    }
257
258    // request a bunch of versions including the deleted version. We should
259    // only get back entries for the versions that exist.
260    Cell kvs[] = getNVersions(ht, FAMILY, 0, 0, Arrays.asList(2L, 3L, 4L, 5L));
261    assertEquals(3, kvs.length);
262    checkOneCell(kvs[0], FAMILY, 0, 0, 5);
263    checkOneCell(kvs[1], FAMILY, 0, 0, 3);
264    checkOneCell(kvs[2], FAMILY, 0, 0, 2);
265
266    ht.close();
267  }
268
269  private void verifyInsertedValues(Table ht, byte[] cf) throws IOException {
270    for (int rowIdx = 0; rowIdx < 5; rowIdx++) {
271      for (int colIdx = 0; colIdx < 5; colIdx++) {
272        // ask for versions that exist.
273        Cell[] kvs = getNVersions(ht, cf, rowIdx, colIdx,
274                                      Arrays.asList(5L, 300L, 6L, 80L));
275        assertEquals(4, kvs.length);
276        checkOneCell(kvs[0], cf, rowIdx, colIdx, 300);
277        checkOneCell(kvs[1], cf, rowIdx, colIdx, 80);
278        checkOneCell(kvs[2], cf, rowIdx, colIdx, 6);
279        checkOneCell(kvs[3], cf, rowIdx, colIdx, 5);
280
281        // ask for versions that do not exist.
282        kvs = getNVersions(ht, cf, rowIdx, colIdx,
283                           Arrays.asList(101L, 102L));
284        assertEquals(0, kvs == null? 0: kvs.length);
285
286        // ask for some versions that exist and some that do not.
287        kvs = getNVersions(ht, cf, rowIdx, colIdx,
288                           Arrays.asList(1L, 300L, 105L, 70L, 115L));
289        assertEquals(3, kvs.length);
290        checkOneCell(kvs[0], cf, rowIdx, colIdx, 300);
291        checkOneCell(kvs[1], cf, rowIdx, colIdx, 70);
292        checkOneCell(kvs[2], cf, rowIdx, colIdx, 1);
293      }
294    }
295  }
296
297  /**
298   * Assert that the passed in KeyValue has expected contents for the
299   * specified row, column & timestamp.
300   */
301  private void checkOneCell(Cell kv, byte[] cf,
302                             int rowIdx, int colIdx, long ts) {
303
304    String ctx = "rowIdx=" + rowIdx + "; colIdx=" + colIdx + "; ts=" + ts;
305
306    assertEquals("Row mismatch which checking: " + ctx,
307                 "row:"+ rowIdx, Bytes.toString(CellUtil.cloneRow(kv)));
308
309    assertEquals("ColumnFamily mismatch while checking: " + ctx,
310                 Bytes.toString(cf), Bytes.toString(CellUtil.cloneFamily(kv)));
311
312    assertEquals("Column qualifier mismatch while checking: " + ctx,
313                 "column:" + colIdx,
314                  Bytes.toString(CellUtil.cloneQualifier(kv)));
315
316    assertEquals("Timestamp mismatch while checking: " + ctx,
317                 ts, kv.getTimestamp());
318
319    assertEquals("Value mismatch while checking: " + ctx,
320                 "value-version-" + ts, Bytes.toString(CellUtil.cloneValue(kv)));
321  }
322
323  /**
324   * Uses the TimestampFilter on a Get to request a specified list of
325   * versions for the row/column specified by rowIdx & colIdx.
326   *
327   */
328  private  Cell[] getNVersions(Table ht, byte[] cf, int rowIdx,
329                                   int colIdx, List<Long> versions)
330    throws IOException {
331    byte row[] = Bytes.toBytes("row:" + rowIdx);
332    byte column[] = Bytes.toBytes("column:" + colIdx);
333    Filter filter = new TimestampsFilter(versions);
334    Get get = new Get(row);
335    get.addColumn(cf, column);
336    get.setFilter(filter);
337    get.setMaxVersions();
338    Result result = ht.get(get);
339
340    return result.rawCells();
341  }
342
343  /**
344   * Uses the TimestampFilter on a Scan to request a specified list of
345   * versions for the rows from startRowIdx to endRowIdx (both inclusive).
346   */
347  private Result[] scanNVersions(Table ht, byte[] cf, int startRowIdx,
348                                 int endRowIdx, List<Long> versions)
349    throws IOException {
350    byte startRow[] = Bytes.toBytes("row:" + startRowIdx);
351    byte endRow[] = Bytes.toBytes("row:" + endRowIdx + 1); // exclusive
352    Filter filter = new TimestampsFilter(versions);
353    Scan scan = new Scan(startRow, endRow);
354    scan.setFilter(filter);
355    scan.setMaxVersions();
356    ResultScanner scanner = ht.getScanner(scan);
357    return scanner.next(endRowIdx - startRowIdx + 1);
358  }
359
360  /**
361   * Insert in specific row/column versions with timestamps
362   * versionStart..versionEnd.
363   */
364  private void putNVersions(Table ht, byte[] cf, int rowIdx, int colIdx,
365                            long versionStart, long versionEnd)
366      throws IOException {
367    byte row[] = Bytes.toBytes("row:" + rowIdx);
368    byte column[] = Bytes.toBytes("column:" + colIdx);
369    Put put = new Put(row);
370    put.setDurability(Durability.SKIP_WAL);
371
372    for (long idx = versionStart; idx <= versionEnd; idx++) {
373      put.addColumn(cf, column, idx, Bytes.toBytes("value-version-" + idx));
374    }
375
376    ht.put(put);
377  }
378
379  /**
380   * For row/column specified by rowIdx/colIdx, delete the cell
381   * corresponding to the specified version.
382   */
383  private void deleteOneVersion(Table ht, byte[] cf, int rowIdx,
384                                int colIdx, long version)
385    throws IOException {
386    byte row[] = Bytes.toBytes("row:" + rowIdx);
387    byte column[] = Bytes.toBytes("column:" + colIdx);
388    Delete del = new Delete(row);
389    del.addColumn(cf, column, version);
390    ht.delete(del);
391  }
392
393}
394
395