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.Arrays;
024import java.util.Collections;
025import java.util.List;
026import org.apache.hadoop.hbase.*;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.testclassification.ClientTests;
029import org.apache.hadoop.hbase.testclassification.LargeTests;
030import org.apache.hadoop.hbase.util.Bytes;
031import org.junit.After;
032import org.junit.AfterClass;
033import org.junit.Before;
034import org.junit.BeforeClass;
035import org.junit.ClassRule;
036import org.junit.Rule;
037import org.junit.Test;
038import org.junit.experimental.categories.Category;
039import org.junit.rules.TestName;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 * Run tests related to {@link org.apache.hadoop.hbase.filter.TimestampsFilter} using HBase client APIs.
045 * Sets up the HBase mini cluster once at start. Each creates a table
046 * named for the method and does its stuff against that.
047 */
048@Category({LargeTests.class, ClientTests.class})
049public class TestMultipleTimestamps {
050
051  @ClassRule
052  public static final HBaseClassTestRule CLASS_RULE =
053      HBaseClassTestRule.forClass(TestMultipleTimestamps.class);
054
055  private static final Logger LOG = LoggerFactory.getLogger(TestMultipleTimestamps.class);
056  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
057
058  @Rule
059  public TestName name = new TestName();
060
061  /**
062   * @throws java.lang.Exception
063   */
064  @BeforeClass
065  public static void setUpBeforeClass() throws Exception {
066    TEST_UTIL.startMiniCluster();
067  }
068
069  /**
070   * @throws java.lang.Exception
071   */
072  @AfterClass
073  public static void tearDownAfterClass() throws Exception {
074    TEST_UTIL.shutdownMiniCluster();
075  }
076
077  /**
078   * @throws java.lang.Exception
079   */
080  @Before
081  public void setUp() throws Exception {
082    // Nothing to do.
083  }
084
085  /**
086   * @throws java.lang.Exception
087   */
088  @After
089  public void tearDown() throws Exception {
090    // Nothing to do.
091  }
092
093  @Test
094  public void testReseeksWithOneColumnMiltipleTimestamp() throws IOException {
095    final TableName tableName = TableName.valueOf(name.getMethodName());
096    byte [] FAMILY = Bytes.toBytes("event_log");
097    byte [][] FAMILIES = new byte[][] { FAMILY };
098
099    // create table; set versions to max...
100    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
101
102    Integer[] putRows = new Integer[] {1, 3, 5, 7};
103    Integer[] putColumns = new Integer[] { 1, 3, 5};
104    Long[] putTimestamps = new Long[] {1L, 2L, 3L, 4L, 5L};
105
106    Integer[] scanRows = new Integer[] {3, 5};
107    Integer[] scanColumns = new Integer[] {3};
108    Long[] scanTimestamps = new Long[] {3L, 4L};
109    int scanMaxVersions = 2;
110
111    put(ht, FAMILY, putRows, putColumns, putTimestamps);
112
113    TEST_UTIL.flush(tableName);
114
115    ResultScanner scanner = scan(ht, FAMILY, scanRows, scanColumns,
116        scanTimestamps, scanMaxVersions);
117
118    Cell [] kvs;
119
120    kvs = scanner.next().rawCells();
121    assertEquals(2, kvs.length);
122    checkOneCell(kvs[0], FAMILY, 3, 3, 4);
123    checkOneCell(kvs[1], FAMILY, 3, 3, 3);
124    kvs = scanner.next().rawCells();
125    assertEquals(2, kvs.length);
126    checkOneCell(kvs[0], FAMILY, 5, 3, 4);
127    checkOneCell(kvs[1], FAMILY, 5, 3, 3);
128
129    ht.close();
130  }
131
132  @Test
133  public void testReseeksWithMultipleColumnOneTimestamp() throws IOException {
134    LOG.info(name.getMethodName());
135    final TableName tableName = TableName.valueOf(name.getMethodName());
136    byte [] FAMILY = Bytes.toBytes("event_log");
137    byte [][] FAMILIES = new byte[][] { FAMILY };
138
139    // create table; set versions to max...
140    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
141
142    Integer[] putRows = new Integer[] {1, 3, 5, 7};
143    Integer[] putColumns = new Integer[] { 1, 3, 5};
144    Long[] putTimestamps = new Long[] {1L, 2L, 3L, 4L, 5L};
145
146    Integer[] scanRows = new Integer[] {3, 5};
147    Integer[] scanColumns = new Integer[] {3,4};
148    Long[] scanTimestamps = new Long[] {3L};
149    int scanMaxVersions = 2;
150
151    put(ht, FAMILY, putRows, putColumns, putTimestamps);
152
153    TEST_UTIL.flush(tableName);
154
155    ResultScanner scanner = scan(ht, FAMILY, scanRows, scanColumns,
156        scanTimestamps, scanMaxVersions);
157
158    Cell[] kvs;
159
160    kvs = scanner.next().rawCells();
161    assertEquals(1, kvs.length);
162    checkOneCell(kvs[0], FAMILY, 3, 3, 3);
163    kvs = scanner.next().rawCells();
164    assertEquals(1, kvs.length);
165    checkOneCell(kvs[0], FAMILY, 5, 3, 3);
166
167    ht.close();
168  }
169
170  @Test
171  public void testReseeksWithMultipleColumnMultipleTimestamp() throws
172  IOException {
173    LOG.info(name.getMethodName());
174
175    final TableName tableName = TableName.valueOf(name.getMethodName());
176    byte [] FAMILY = Bytes.toBytes("event_log");
177    byte [][] FAMILIES = new byte[][] { FAMILY };
178
179    // create table; set versions to max...
180    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
181
182    Integer[] putRows = new Integer[] {1, 3, 5, 7};
183    Integer[] putColumns = new Integer[] { 1, 3, 5};
184    Long[] putTimestamps = new Long[] {1L, 2L, 3L, 4L, 5L};
185
186    Integer[] scanRows = new Integer[] {5, 7};
187    Integer[] scanColumns = new Integer[] {3, 4, 5};
188    Long[] scanTimestamps = new Long[] { 2L, 3L};
189    int scanMaxVersions = 2;
190
191    put(ht, FAMILY, putRows, putColumns, putTimestamps);
192
193    TEST_UTIL.flush(tableName);
194    Scan scan = new Scan();
195    scan.setMaxVersions(10);
196    ResultScanner scanner = ht.getScanner(scan);
197    while (true) {
198      Result r = scanner.next();
199      if (r == null) break;
200      LOG.info("r=" + r);
201    }
202    scanner = scan(ht, FAMILY, scanRows, scanColumns, scanTimestamps, scanMaxVersions);
203
204    Cell[] kvs;
205
206    // This looks like wrong answer.  Should be 2.  Even then we are returning wrong result,
207    // timestamps that are 3 whereas should be 2 since min is inclusive.
208    kvs = scanner.next().rawCells();
209    assertEquals(4, kvs.length);
210    checkOneCell(kvs[0], FAMILY, 5, 3, 3);
211    checkOneCell(kvs[1], FAMILY, 5, 3, 2);
212    checkOneCell(kvs[2], FAMILY, 5, 5, 3);
213    checkOneCell(kvs[3], FAMILY, 5, 5, 2);
214    kvs = scanner.next().rawCells();
215    assertEquals(4, kvs.length);
216    checkOneCell(kvs[0], FAMILY, 7, 3, 3);
217    checkOneCell(kvs[1], FAMILY, 7, 3, 2);
218    checkOneCell(kvs[2], FAMILY, 7, 5, 3);
219    checkOneCell(kvs[3], FAMILY, 7, 5, 2);
220
221    ht.close();
222  }
223
224  @Test
225  public void testReseeksWithMultipleFiles() throws IOException {
226    LOG.info(name.getMethodName());
227    final TableName tableName = TableName.valueOf(name.getMethodName());
228    byte [] FAMILY = Bytes.toBytes("event_log");
229    byte [][] FAMILIES = new byte[][] { FAMILY };
230
231    // create table; set versions to max...
232    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
233
234    Integer[] putRows1 = new Integer[] {1, 2, 3};
235    Integer[] putColumns1 = new Integer[] { 2, 5, 6};
236    Long[] putTimestamps1 = new Long[] {1L, 2L, 5L};
237
238    Integer[] putRows2 = new Integer[] {6, 7};
239    Integer[] putColumns2 = new Integer[] {3, 6};
240    Long[] putTimestamps2 = new Long[] {4L, 5L};
241
242    Integer[] putRows3 = new Integer[] {2, 3, 5};
243    Integer[] putColumns3 = new Integer[] {1, 2, 3};
244    Long[] putTimestamps3 = new Long[] {4L,8L};
245
246
247    Integer[] scanRows = new Integer[] {3, 5, 7};
248    Integer[] scanColumns = new Integer[] {3, 4, 5};
249    Long[] scanTimestamps = new Long[] { 2L, 4L};
250    int scanMaxVersions = 5;
251
252    put(ht, FAMILY, putRows1, putColumns1, putTimestamps1);
253    TEST_UTIL.flush(tableName);
254    put(ht, FAMILY, putRows2, putColumns2, putTimestamps2);
255    TEST_UTIL.flush(tableName);
256    put(ht, FAMILY, putRows3, putColumns3, putTimestamps3);
257
258    ResultScanner scanner = scan(ht, FAMILY, scanRows, scanColumns,
259        scanTimestamps, scanMaxVersions);
260
261    Cell[] kvs;
262
263    kvs = scanner.next().rawCells();
264    assertEquals(2, kvs.length);
265    checkOneCell(kvs[0], FAMILY, 3, 3, 4);
266    checkOneCell(kvs[1], FAMILY, 3, 5, 2);
267
268    kvs = scanner.next().rawCells();
269    assertEquals(1, kvs.length);
270    checkOneCell(kvs[0], FAMILY, 5, 3, 4);
271
272    kvs = scanner.next().rawCells();
273    assertEquals(1, kvs.length);
274    checkOneCell(kvs[0], FAMILY, 6, 3, 4);
275
276    kvs = scanner.next().rawCells();
277    assertEquals(1, kvs.length);
278    checkOneCell(kvs[0], FAMILY, 7, 3, 4);
279
280    ht.close();
281  }
282
283  @Test
284  public void testWithVersionDeletes() throws Exception {
285
286    // first test from memstore (without flushing).
287    testWithVersionDeletes(false);
288
289    // run same test against HFiles (by forcing a flush).
290    testWithVersionDeletes(true);
291  }
292
293  public void testWithVersionDeletes(boolean flushTables) throws IOException {
294    LOG.info(name.getMethodName() + "_"+ (flushTables ? "flush" : "noflush"));
295    final TableName tableName = TableName.valueOf(name.getMethodName() + "_" + (flushTables ?
296            "flush" : "noflush"));
297    byte [] FAMILY = Bytes.toBytes("event_log");
298    byte [][] FAMILIES = new byte[][] { FAMILY };
299
300    // create table; set versions to max...
301    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
302
303    // For row:0, col:0: insert versions 1 through 5.
304    putNVersions(ht, FAMILY, 0, 0, 1, 5);
305
306    if (flushTables) {
307      TEST_UTIL.flush(tableName);
308    }
309
310    // delete version 4.
311    deleteOneVersion(ht, FAMILY, 0, 0, 4);
312
313    // request a bunch of versions including the deleted version. We should
314    // only get back entries for the versions that exist.
315    Cell kvs[] = getNVersions(ht, FAMILY, 0, 0,
316        Arrays.asList(2L, 3L, 4L, 5L));
317    assertEquals(3, kvs.length);
318    checkOneCell(kvs[0], FAMILY, 0, 0, 5);
319    checkOneCell(kvs[1], FAMILY, 0, 0, 3);
320    checkOneCell(kvs[2], FAMILY, 0, 0, 2);
321
322    ht.close();
323  }
324
325  @Test
326  public void testWithMultipleVersionDeletes() throws IOException {
327    LOG.info(name.getMethodName());
328
329    final TableName tableName = TableName.valueOf(name.getMethodName());
330    byte [] FAMILY = Bytes.toBytes("event_log");
331    byte [][] FAMILIES = new byte[][] { FAMILY };
332
333    // create table; set versions to max...
334    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
335
336    // For row:0, col:0: insert versions 1 through 5.
337    putNVersions(ht, FAMILY, 0, 0, 1, 5);
338
339    TEST_UTIL.flush(tableName);
340
341    // delete all versions before 4.
342    deleteAllVersionsBefore(ht, FAMILY, 0, 0, 4);
343
344    // request a bunch of versions including the deleted version. We should
345    // only get back entries for the versions that exist.
346    Cell kvs[] = getNVersions(ht, FAMILY, 0, 0, Arrays.asList(2L, 3L));
347    assertEquals(0, kvs.length);
348
349    ht.close();
350  }
351
352  @Test
353  public void testWithColumnDeletes() throws IOException {
354    final TableName tableName = TableName.valueOf(name.getMethodName());
355    byte [] FAMILY = Bytes.toBytes("event_log");
356    byte [][] FAMILIES = new byte[][] { FAMILY };
357
358    // create table; set versions to max...
359    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
360
361    // For row:0, col:0: insert versions 1 through 5.
362    putNVersions(ht, FAMILY, 0, 0, 1, 5);
363
364    TEST_UTIL.flush(tableName);
365
366    // delete all versions before 4.
367    deleteColumn(ht, FAMILY, 0, 0);
368
369    // request a bunch of versions including the deleted version. We should
370    // only get back entries for the versions that exist.
371    Cell kvs[] = getNVersions(ht, FAMILY, 0, 0, Arrays.asList(2L, 3L));
372    assertEquals(0, kvs.length);
373
374    ht.close();
375  }
376
377  @Test
378  public void testWithFamilyDeletes() throws IOException {
379    final TableName tableName = TableName.valueOf(name.getMethodName());
380    byte [] FAMILY = Bytes.toBytes("event_log");
381    byte [][] FAMILIES = new byte[][] { FAMILY };
382
383    // create table; set versions to max...
384    Table ht = TEST_UTIL.createTable(tableName, FAMILIES, Integer.MAX_VALUE);
385
386    // For row:0, col:0: insert versions 1 through 5.
387    putNVersions(ht, FAMILY, 0, 0, 1, 5);
388
389    TEST_UTIL.flush(tableName);
390
391    // delete all versions before 4.
392    deleteFamily(ht, FAMILY, 0);
393
394    // request a bunch of versions including the deleted version. We should
395    // only get back entries for the versions that exist.
396    Cell kvs[] = getNVersions(ht, FAMILY, 0, 0, Arrays.asList(2L, 3L));
397    assertEquals(0, kvs.length);
398
399    ht.close();
400  }
401
402  /**
403   * Assert that the passed in KeyValue has expected contents for the
404   * specified row, column & timestamp.
405   */
406  private void checkOneCell(Cell kv, byte[] cf,
407      int rowIdx, int colIdx, long ts) {
408
409    String ctx = "rowIdx=" + rowIdx + "; colIdx=" + colIdx + "; ts=" + ts;
410
411    assertEquals("Row mismatch which checking: " + ctx,
412        "row:"+ rowIdx, Bytes.toString(CellUtil.cloneRow(kv)));
413
414    assertEquals("ColumnFamily mismatch while checking: " + ctx,
415        Bytes.toString(cf), Bytes.toString(CellUtil.cloneFamily(kv)));
416
417    assertEquals("Column qualifier mismatch while checking: " + ctx,
418        "column:" + colIdx,
419        Bytes.toString(CellUtil.cloneQualifier(kv)));
420
421    assertEquals("Timestamp mismatch while checking: " + ctx,
422        ts, kv.getTimestamp());
423
424    assertEquals("Value mismatch while checking: " + ctx,
425        "value-version-" + ts, Bytes.toString(CellUtil.cloneValue(kv)));
426  }
427
428  /**
429   * Uses the TimestampFilter on a Get to request a specified list of
430   * versions for the row/column specified by rowIdx & colIdx.
431   *
432   */
433  private  Cell[] getNVersions(Table ht, byte[] cf, int rowIdx,
434      int colIdx, List<Long> versions)
435  throws IOException {
436    byte row[] = Bytes.toBytes("row:" + rowIdx);
437    byte column[] = Bytes.toBytes("column:" + colIdx);
438    Get get = new Get(row);
439    get.addColumn(cf, column);
440    get.setMaxVersions();
441    get.setTimeRange(Collections.min(versions), Collections.max(versions)+1);
442    Result result = ht.get(get);
443
444    return result.rawCells();
445  }
446
447  private  ResultScanner scan(Table ht, byte[] cf,
448      Integer[] rowIndexes, Integer[] columnIndexes,
449      Long[] versions, int maxVersions)
450  throws IOException {
451    Arrays.asList(rowIndexes);
452    byte startRow[] = Bytes.toBytes("row:" +
453        Collections.min( Arrays.asList(rowIndexes)));
454    byte endRow[] = Bytes.toBytes("row:" +
455        Collections.max( Arrays.asList(rowIndexes))+1);
456    Scan scan = new Scan(startRow, endRow);
457    for (Integer colIdx: columnIndexes) {
458      byte column[] = Bytes.toBytes("column:" + colIdx);
459      scan.addColumn(cf, column);
460    }
461    scan.setMaxVersions(maxVersions);
462    scan.setTimeRange(Collections.min(Arrays.asList(versions)),
463        Collections.max(Arrays.asList(versions))+1);
464    ResultScanner scanner = ht.getScanner(scan);
465    return scanner;
466  }
467
468  private void put(Table ht, byte[] cf, Integer[] rowIndexes,
469      Integer[] columnIndexes, Long[] versions)
470  throws IOException {
471    for (int rowIdx: rowIndexes) {
472      byte row[] = Bytes.toBytes("row:" + rowIdx);
473      Put put = new Put(row);
474      put.setDurability(Durability.SKIP_WAL);
475      for(int colIdx: columnIndexes) {
476        byte column[] = Bytes.toBytes("column:" + colIdx);
477        for (long version: versions) {
478          put.addColumn(cf, column, version, Bytes.toBytes("value-version-" +
479                  version));
480        }
481      }
482      ht.put(put);
483    }
484  }
485
486  /**
487   * Insert in specific row/column versions with timestamps
488   * versionStart..versionEnd.
489   */
490  private void putNVersions(Table ht, byte[] cf, int rowIdx, int colIdx,
491      long versionStart, long versionEnd)
492  throws IOException {
493    byte row[] = Bytes.toBytes("row:" + rowIdx);
494    byte column[] = Bytes.toBytes("column:" + colIdx);
495    Put put = new Put(row);
496    put.setDurability(Durability.SKIP_WAL);
497
498    for (long idx = versionStart; idx <= versionEnd; idx++) {
499      put.addColumn(cf, column, idx, Bytes.toBytes("value-version-" + idx));
500    }
501
502    ht.put(put);
503  }
504
505  /**
506   * For row/column specified by rowIdx/colIdx, delete the cell
507   * corresponding to the specified version.
508   */
509  private void deleteOneVersion(Table ht, byte[] cf, int rowIdx,
510      int colIdx, long version)
511  throws IOException {
512    byte row[] = Bytes.toBytes("row:" + rowIdx);
513    byte column[] = Bytes.toBytes("column:" + colIdx);
514    Delete del = new Delete(row);
515    del.addColumn(cf, column, version);
516    ht.delete(del);
517  }
518
519  /**
520   * For row/column specified by rowIdx/colIdx, delete all cells
521   * preceeding the specified version.
522   */
523  private void deleteAllVersionsBefore(Table ht, byte[] cf, int rowIdx,
524      int colIdx, long version)
525  throws IOException {
526    byte row[] = Bytes.toBytes("row:" + rowIdx);
527    byte column[] = Bytes.toBytes("column:" + colIdx);
528    Delete del = new Delete(row);
529    del.addColumns(cf, column, version);
530    ht.delete(del);
531  }
532
533  private void deleteColumn(Table ht, byte[] cf, int rowIdx, int colIdx) throws IOException {
534    byte row[] = Bytes.toBytes("row:" + rowIdx);
535    byte column[] = Bytes.toBytes("column:" + colIdx);
536    Delete del = new Delete(row);
537    del.addColumns(cf, column);
538    ht.delete(del);
539  }
540
541  private void deleteFamily(Table ht, byte[] cf, int rowIdx) throws IOException {
542    byte row[] = Bytes.toBytes("row:" + rowIdx);
543    Delete del = new Delete(row);
544    del.addFamily(cf);
545    ht.delete(del);
546  }
547
548}
549
550