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.regionserver;
019
020import static org.apache.hadoop.hbase.CellUtil.createCell;
021import static org.apache.hadoop.hbase.KeyValueTestUtil.create;
022import static org.apache.hadoop.hbase.regionserver.KeyValueScanFixture.scanFixture;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertNull;
026import static org.junit.Assert.assertTrue;
027
028import java.io.IOException;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.List;
033import java.util.NavigableSet;
034import java.util.OptionalInt;
035import java.util.TreeSet;
036import java.util.concurrent.atomic.AtomicInteger;
037
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.hbase.Cell;
040import org.apache.hadoop.hbase.CellComparator;
041import org.apache.hadoop.hbase.CellUtil;
042import org.apache.hadoop.hbase.HBaseClassTestRule;
043import org.apache.hadoop.hbase.HBaseConfiguration;
044import org.apache.hadoop.hbase.HBaseTestingUtility;
045import org.apache.hadoop.hbase.HConstants;
046import org.apache.hadoop.hbase.KeepDeletedCells;
047import org.apache.hadoop.hbase.KeyValue;
048import org.apache.hadoop.hbase.PrivateCellUtil;
049import org.apache.hadoop.hbase.client.Get;
050import org.apache.hadoop.hbase.client.Scan;
051import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
052import org.apache.hadoop.hbase.testclassification.MediumTests;
053import org.apache.hadoop.hbase.testclassification.RegionServerTests;
054import org.apache.hadoop.hbase.util.Bytes;
055import org.apache.hadoop.hbase.util.EnvironmentEdge;
056import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
057import org.junit.ClassRule;
058import org.junit.Ignore;
059import org.junit.Rule;
060import org.junit.Test;
061import org.junit.experimental.categories.Category;
062import org.junit.rules.TestName;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066// Can't be small as it plays with EnvironmentEdgeManager
067@Category({RegionServerTests.class, MediumTests.class})
068public class TestStoreScanner {
069
070  @ClassRule
071  public static final HBaseClassTestRule CLASS_RULE =
072      HBaseClassTestRule.forClass(TestStoreScanner.class);
073
074  private static final Logger LOG = LoggerFactory.getLogger(TestStoreScanner.class);
075  @Rule public TestName name = new TestName();
076  private static final String CF_STR = "cf";
077  private static final byte[] CF = Bytes.toBytes(CF_STR);
078  static Configuration CONF = HBaseConfiguration.create();
079  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
080  private ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, Integer.MAX_VALUE, Long.MAX_VALUE,
081      KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
082
083  /**
084   * From here on down, we have a bunch of defines and specific CELL_GRID of Cells. The
085   * CELL_GRID then has a Scanner that can fake out 'block' transitions. All this elaborate
086   * setup is for tests that ensure we don't overread, and that the {@link StoreScanner} is not
087   * overly enthusiastic.
088   */
089  private static final byte[] ZERO = new byte[] {'0'};
090  private static final byte[] ZERO_POINT_ZERO = new byte[] {'0', '.', '0'};
091  private static final byte[] ONE = new byte[] {'1'};
092  private static final byte[] TWO = new byte[] {'2'};
093  private static final byte[] TWO_POINT_TWO = new byte[] {'2', '.', '2'};
094  private static final byte[] THREE = new byte[] {'3'};
095  private static final byte[] FOUR = new byte[] {'4'};
096  private static final byte[] FIVE = new byte[] {'5'};
097  private static final byte[] VALUE = new byte[] {'v'};
098  private static final int CELL_GRID_BLOCK2_BOUNDARY = 4;
099  private static final int CELL_GRID_BLOCK3_BOUNDARY = 11;
100  private static final int CELL_GRID_BLOCK4_BOUNDARY = 15;
101  private static final int CELL_GRID_BLOCK5_BOUNDARY = 19;
102
103  /**
104   * Five rows by four columns distinguished by column qualifier (column qualifier is one of the
105   * four rows... ONE, TWO, etc.). Exceptions are a weird row after TWO; it is TWO_POINT_TWO.
106   * And then row FOUR has five columns finishing w/ row FIVE having a single column.
107   * We will use this to test scan does the right thing as it
108   * we do Gets, StoreScanner#optimize, and what we do on (faked) block boundaries.
109   */
110  private static final Cell[] CELL_GRID = new Cell [] {
111    createCell(ONE, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
112    createCell(ONE, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
113    createCell(ONE, CF, THREE, 1L, KeyValue.Type.Put.getCode(), VALUE),
114    createCell(ONE, CF, FOUR, 1L, KeyValue.Type.Put.getCode(), VALUE),
115    // Offset 4 CELL_GRID_BLOCK2_BOUNDARY
116    createCell(TWO, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
117    createCell(TWO, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
118    createCell(TWO, CF, THREE, 1L, KeyValue.Type.Put.getCode(), VALUE),
119    createCell(TWO, CF, FOUR, 1L, KeyValue.Type.Put.getCode(), VALUE),
120    createCell(TWO_POINT_TWO, CF, ZERO, 1L, KeyValue.Type.Put.getCode(), VALUE),
121    createCell(TWO_POINT_TWO, CF, ZERO_POINT_ZERO, 1L, KeyValue.Type.Put.getCode(), VALUE),
122    createCell(TWO_POINT_TWO, CF, FIVE, 1L, KeyValue.Type.Put.getCode(), VALUE),
123    // Offset 11! CELL_GRID_BLOCK3_BOUNDARY
124    createCell(THREE, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
125    createCell(THREE, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
126    createCell(THREE, CF, THREE, 1L, KeyValue.Type.Put.getCode(), VALUE),
127    createCell(THREE, CF, FOUR, 1L, KeyValue.Type.Put.getCode(), VALUE),
128    // Offset 15 CELL_GRID_BLOCK4_BOUNDARY
129    createCell(FOUR, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
130    createCell(FOUR, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
131    createCell(FOUR, CF, THREE, 1L, KeyValue.Type.Put.getCode(), VALUE),
132    createCell(FOUR, CF, FOUR, 1L, KeyValue.Type.Put.getCode(), VALUE),
133    // Offset 19 CELL_GRID_BLOCK5_BOUNDARY
134    createCell(FOUR, CF, FIVE, 1L, KeyValue.Type.Put.getCode(), VALUE),
135    createCell(FIVE, CF, ZERO, 1L, KeyValue.Type.Put.getCode(), VALUE),
136  };
137
138  private static class KeyValueHeapWithCount extends KeyValueHeap {
139
140    final AtomicInteger count;
141
142    public KeyValueHeapWithCount(List<? extends KeyValueScanner> scanners,
143        CellComparator comparator, AtomicInteger count) throws IOException {
144      super(scanners, comparator);
145      this.count = count;
146    }
147
148    @Override
149    public Cell peek() {
150      this.count.incrementAndGet();
151      return super.peek();
152    }
153  }
154
155  /**
156   * A StoreScanner for our CELL_GRID above. Fakes the block transitions. Does counts of
157   * calls to optimize and counts of when optimize actually did an optimize.
158   */
159  private static class CellGridStoreScanner extends StoreScanner {
160    // Count of how often optimize is called and of how often it does an optimize.
161    AtomicInteger count;
162    final AtomicInteger optimization = new AtomicInteger(0);
163
164    CellGridStoreScanner(final Scan scan, ScanInfo scanInfo) throws IOException {
165      super(scan, scanInfo, scan.getFamilyMap().get(CF), Arrays.<KeyValueScanner> asList(
166        new KeyValueScanner[] { new KeyValueScanFixture(CellComparator.getInstance(), CELL_GRID) }));
167    }
168
169    @Override
170    protected void resetKVHeap(List<? extends KeyValueScanner> scanners,
171        CellComparator comparator) throws IOException {
172      if (count == null) {
173        count = new AtomicInteger(0);
174      }
175      heap = newKVHeap(scanners, comparator);
176    }
177
178    @Override
179    protected KeyValueHeap newKVHeap(List<? extends KeyValueScanner> scanners,
180        CellComparator comparator) throws IOException {
181      return new KeyValueHeapWithCount(scanners, comparator, count);
182    }
183
184    @Override
185    protected boolean trySkipToNextRow(Cell cell) throws IOException {
186      boolean optimized = super.trySkipToNextRow(cell);
187      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
188          + ", optimized=" + optimized);
189      if (optimized) {
190        optimization.incrementAndGet();
191      }
192      return optimized;
193    }
194
195    @Override
196    protected boolean trySkipToNextColumn(Cell cell) throws IOException {
197      boolean optimized = super.trySkipToNextColumn(cell);
198      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
199          + ", optimized=" + optimized);
200      if (optimized) {
201        optimization.incrementAndGet();
202      }
203      return optimized;
204    }
205
206    @Override
207    public Cell getNextIndexedKey() {
208      // Fake block boundaries by having index of next block change as we go through scan.
209      return count.get() > CELL_GRID_BLOCK4_BOUNDARY?
210          PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK5_BOUNDARY]):
211            count.get() > CELL_GRID_BLOCK3_BOUNDARY?
212                PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK4_BOUNDARY]):
213                  count.get() > CELL_GRID_BLOCK2_BOUNDARY?
214                      PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK3_BOUNDARY]):
215                        PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK2_BOUNDARY]);
216    }
217  };
218
219  private static final int CELL_WITH_VERSIONS_BLOCK2_BOUNDARY = 4;
220
221  private static final Cell[] CELL_WITH_VERSIONS = new Cell [] {
222    createCell(ONE, CF, ONE, 2L, KeyValue.Type.Put.getCode(), VALUE),
223    createCell(ONE, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
224    createCell(ONE, CF, TWO, 2L, KeyValue.Type.Put.getCode(), VALUE),
225    createCell(ONE, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
226    // Offset 4 CELL_WITH_VERSIONS_BLOCK2_BOUNDARY
227    createCell(TWO, CF, ONE, 1L, KeyValue.Type.Put.getCode(), VALUE),
228    createCell(TWO, CF, TWO, 1L, KeyValue.Type.Put.getCode(), VALUE),
229  };
230
231  private static class CellWithVersionsStoreScanner extends StoreScanner {
232    // Count of how often optimize is called and of how often it does an optimize.
233    final AtomicInteger optimization = new AtomicInteger(0);
234
235    CellWithVersionsStoreScanner(final Scan scan, ScanInfo scanInfo) throws IOException {
236      super(scan, scanInfo, scan.getFamilyMap().get(CF),
237          Arrays.<KeyValueScanner> asList(new KeyValueScanner[] {
238              new KeyValueScanFixture(CellComparator.getInstance(), CELL_WITH_VERSIONS) }));
239    }
240
241    @Override
242    protected boolean trySkipToNextColumn(Cell cell) throws IOException {
243      boolean optimized = super.trySkipToNextColumn(cell);
244      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
245          + ", optimized=" + optimized);
246      if (optimized) {
247        optimization.incrementAndGet();
248      }
249      return optimized;
250    }
251
252    @Override
253    public Cell getNextIndexedKey() {
254      // Fake block boundaries by having index of next block change as we go through scan.
255      return PrivateCellUtil
256          .createFirstOnRow(CELL_WITH_VERSIONS[CELL_WITH_VERSIONS_BLOCK2_BOUNDARY]);
257    }
258  };
259
260  private static class CellWithVersionsNoOptimizeStoreScanner extends StoreScanner {
261    // Count of how often optimize is called and of how often it does an optimize.
262    final AtomicInteger optimization = new AtomicInteger(0);
263
264    CellWithVersionsNoOptimizeStoreScanner(Scan scan, ScanInfo scanInfo) throws IOException {
265      super(scan, scanInfo, scan.getFamilyMap().get(CF),
266          Arrays.<KeyValueScanner> asList(new KeyValueScanner[] {
267              new KeyValueScanFixture(CellComparator.getInstance(), CELL_WITH_VERSIONS) }));
268    }
269
270    @Override
271    protected boolean trySkipToNextColumn(Cell cell) throws IOException {
272      boolean optimized = super.trySkipToNextColumn(cell);
273      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
274          + ", optimized=" + optimized);
275      if (optimized) {
276        optimization.incrementAndGet();
277      }
278      return optimized;
279    }
280
281    @Override
282    public Cell getNextIndexedKey() {
283      return null;
284    }
285  };
286
287  @Test
288  public void testWithColumnCountGetFilter() throws Exception {
289    Get get = new Get(ONE);
290    get.readAllVersions();
291    get.addFamily(CF);
292    get.setFilter(new ColumnCountGetFilter(2));
293
294    try (CellWithVersionsNoOptimizeStoreScanner scannerNoOptimize =
295        new CellWithVersionsNoOptimizeStoreScanner(new Scan(get), this.scanInfo)) {
296      List<Cell> results = new ArrayList<>();
297      while (scannerNoOptimize.next(results)) {
298        continue;
299      }
300      assertEquals(2, results.size());
301      assertTrue(CellUtil.matchingColumn(results.get(0), CELL_WITH_VERSIONS[0]));
302      assertTrue(CellUtil.matchingColumn(results.get(1), CELL_WITH_VERSIONS[2]));
303      assertTrue("Optimize should do some optimizations",
304        scannerNoOptimize.optimization.get() == 0);
305    }
306
307    get.setFilter(new ColumnCountGetFilter(2));
308    try (CellWithVersionsStoreScanner scanner =
309        new CellWithVersionsStoreScanner(new Scan(get), this.scanInfo)) {
310      List<Cell> results = new ArrayList<>();
311      while (scanner.next(results)) {
312        continue;
313      }
314      assertEquals(2, results.size());
315      assertTrue(CellUtil.matchingColumn(results.get(0), CELL_WITH_VERSIONS[0]));
316      assertTrue(CellUtil.matchingColumn(results.get(1), CELL_WITH_VERSIONS[2]));
317      assertTrue("Optimize should do some optimizations", scanner.optimization.get() > 0);
318    }
319  }
320
321  /*
322   * Test utility for building a NavigableSet for scanners.
323   * @param strCols
324   * @return
325   */
326  NavigableSet<byte[]> getCols(String ...strCols) {
327    NavigableSet<byte[]> cols = new TreeSet<>(Bytes.BYTES_COMPARATOR);
328    for (String col : strCols) {
329      byte[] bytes = Bytes.toBytes(col);
330      cols.add(bytes);
331    }
332    return cols;
333  }
334
335  @Test
336  public void testFullRowGetDoesNotOverreadWhenRowInsideOneBlock() throws IOException {
337    // Do a Get against row two. Row two is inside a block that starts with row TWO but ends with
338    // row TWO_POINT_TWO. We should read one block only.
339    Get get = new Get(TWO);
340    Scan scan = new Scan(get);
341    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
342      List<Cell> results = new ArrayList<>();
343      while (scanner.next(results)) {
344        continue;
345      }
346      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
347      // TWO_POINT_TWO row does not have a a column ONE.
348      assertEquals(4, results.size());
349      // We should have gone the optimize route 5 times totally... an INCLUDE for the four cells
350      // in the row plus the DONE on the end.
351      assertEquals(5, scanner.count.get());
352      // For a full row Get, there should be no opportunity for scanner optimization.
353      assertEquals(0, scanner.optimization.get());
354    }
355  }
356
357  @Test
358  public void testFullRowSpansBlocks() throws IOException {
359    // Do a Get against row FOUR. It spans two blocks.
360    Get get = new Get(FOUR);
361    Scan scan = new Scan(get);
362    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
363      List<Cell> results = new ArrayList<>();
364      while (scanner.next(results)) {
365        continue;
366      }
367      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
368      // TWO_POINT_TWO row does not have a a column ONE.
369      assertEquals(5, results.size());
370      // We should have gone the optimize route 6 times totally... an INCLUDE for the five cells
371      // in the row plus the DONE on the end.
372      assertEquals(6, scanner.count.get());
373      // For a full row Get, there should be no opportunity for scanner optimization.
374      assertEquals(0, scanner.optimization.get());
375    }
376  }
377
378  /**
379   * Test optimize in StoreScanner. Test that we skip to the next 'block' when we it makes sense
380   * reading the block 'index'.
381   * @throws IOException
382   */
383  @Test
384  public void testOptimize() throws IOException {
385    Scan scan = new Scan();
386    // A scan that just gets the first qualifier on each row of the CELL_GRID
387    scan.addColumn(CF, ONE);
388    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
389      List<Cell> results = new ArrayList<>();
390      while (scanner.next(results)) {
391        continue;
392      }
393      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
394      // TWO_POINT_TWO row does not have a a column ONE.
395      assertEquals(4, results.size());
396      for (Cell cell: results) {
397        assertTrue(Bytes.equals(ONE, 0, ONE.length,
398            cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
399      }
400      assertTrue("Optimize should do some optimizations", scanner.optimization.get() > 0);
401    }
402  }
403
404  /**
405   * Ensure the optimize Scan method in StoreScanner does not get in the way of a Get doing minimum
406   * work... seeking to start of block and then SKIPPING until we find the wanted Cell.
407   * This 'simple' scenario mimics case of all Cells fitting inside a single HFileBlock.
408   * See HBASE-15392. This test is a little cryptic. Takes a bit of staring to figure what it up to.
409   * @throws IOException
410   */
411  @Test
412  public void testOptimizeAndGet() throws IOException {
413    // First test a Get of two columns in the row R2. Every Get is a Scan. Get columns named
414    // R2 and R3.
415    Get get = new Get(TWO);
416    get.addColumn(CF, TWO);
417    get.addColumn(CF, THREE);
418    Scan scan = new Scan(get);
419    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
420      List<Cell> results = new ArrayList<>();
421      // For a Get there should be no more next's after the first call.
422      assertEquals(false, scanner.next(results));
423      // Should be one result only.
424      assertEquals(2, results.size());
425      // And we should have gone through optimize twice only.
426      assertEquals("First qcode is SEEK_NEXT_COL and second INCLUDE_AND_SEEK_NEXT_ROW", 3,
427        scanner.count.get());
428    }
429  }
430
431  /**
432   * Ensure that optimize does not cause the Get to do more seeking than required. Optimize
433   * (see HBASE-15392) was causing us to seek all Cells in a block when a Get Scan if the next block
434   * index/start key was a different row to the current one. A bug. We'd call next too often
435   * because we had to exhaust all Cells in the current row making us load the next block just to
436   * discard what we read there. This test is a little cryptic. Takes a bit of staring to figure
437   * what it up to.
438   * @throws IOException
439   */
440  @Test
441  public void testOptimizeAndGetWithFakedNextBlockIndexStart() throws IOException {
442    // First test a Get of second column in the row R2. Every Get is a Scan. Second column has a
443    // qualifier of R2.
444    Get get = new Get(THREE);
445    get.addColumn(CF, TWO);
446    Scan scan = new Scan(get);
447    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
448      List<Cell> results = new ArrayList<>();
449      // For a Get there should be no more next's after the first call.
450      assertEquals(false, scanner.next(results));
451      // Should be one result only.
452      assertEquals(1, results.size());
453      // And we should have gone through optimize twice only.
454      assertEquals("First qcode is SEEK_NEXT_COL and second INCLUDE_AND_SEEK_NEXT_ROW", 2,
455        scanner.count.get());
456    }
457  }
458
459  @Test
460  public void testScanTimeRange() throws IOException {
461    String r1 = "R1";
462    // returns only 1 of these 2 even though same timestamp
463    KeyValue [] kvs = new KeyValue[] {
464        create(r1, CF_STR, "a", 1, KeyValue.Type.Put, "dont-care"),
465        create(r1, CF_STR, "a", 2, KeyValue.Type.Put, "dont-care"),
466        create(r1, CF_STR, "a", 3, KeyValue.Type.Put, "dont-care"),
467        create(r1, CF_STR, "a", 4, KeyValue.Type.Put, "dont-care"),
468        create(r1, CF_STR, "a", 5, KeyValue.Type.Put, "dont-care"),
469    };
470    List<KeyValueScanner> scanners = Arrays.<KeyValueScanner>asList(
471        new KeyValueScanner[] {
472            new KeyValueScanFixture(CellComparator.getInstance(), kvs)
473    });
474    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
475    scanSpec.setTimeRange(0, 6);
476    scanSpec.readAllVersions();
477    List<Cell> results = null;
478    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
479      results = new ArrayList<>();
480      assertEquals(true, scan.next(results));
481      assertEquals(5, results.size());
482      assertEquals(kvs[kvs.length - 1], results.get(0));
483    }
484    // Scan limited TimeRange
485    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
486    scanSpec.setTimeRange(1, 3);
487    scanSpec.readAllVersions();
488    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
489      results = new ArrayList<>();
490      assertEquals(true, scan.next(results));
491      assertEquals(2, results.size());
492    }
493    // Another range.
494    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
495    scanSpec.setTimeRange(5, 10);
496    scanSpec.readAllVersions();
497    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
498      results = new ArrayList<>();
499      assertEquals(true, scan.next(results));
500      assertEquals(1, results.size());
501    }
502    // See how TimeRange and Versions interact.
503    // Another range.
504    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
505    scanSpec.setTimeRange(0, 10);
506    scanSpec.readVersions(3);
507    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
508      results = new ArrayList<>();
509      assertEquals(true, scan.next(results));
510      assertEquals(3, results.size());
511    }
512  }
513
514  @Test
515  public void testScanSameTimestamp() throws IOException {
516    // returns only 1 of these 2 even though same timestamp
517    KeyValue [] kvs = new KeyValue[] {
518        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
519        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
520    };
521    List<KeyValueScanner> scanners = Arrays.asList(
522        new KeyValueScanner[] {
523            new KeyValueScanFixture(CellComparator.getInstance(), kvs)
524        });
525
526    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
527    // this only uses maxVersions (default=1) and TimeRange (default=all)
528    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
529      List<Cell> results = new ArrayList<>();
530      assertEquals(true, scan.next(results));
531      assertEquals(1, results.size());
532      assertEquals(kvs[0], results.get(0));
533    }
534  }
535
536  /*
537   * Test test shows exactly how the matcher's return codes confuses the StoreScanner
538   * and prevent it from doing the right thing.  Seeking once, then nexting twice
539   * should return R1, then R2, but in this case it doesnt.
540   * TODO this comment makes no sense above. Appears to do the right thing.
541   * @throws IOException
542   */
543  @Test
544  public void testWontNextToNext() throws IOException {
545    // build the scan file:
546    KeyValue [] kvs = new KeyValue[] {
547        create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
548        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
549        create("R2", "cf", "a", 1, KeyValue.Type.Put, "dont-care")
550    };
551    List<KeyValueScanner> scanners = scanFixture(kvs);
552
553    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
554    // this only uses maxVersions (default=1) and TimeRange (default=all)
555    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
556      List<Cell> results = new ArrayList<>();
557      scan.next(results);
558      assertEquals(1, results.size());
559      assertEquals(kvs[0], results.get(0));
560      // should be ok...
561      // now scan _next_ again.
562      results.clear();
563      scan.next(results);
564      assertEquals(1, results.size());
565      assertEquals(kvs[2], results.get(0));
566
567      results.clear();
568      scan.next(results);
569      assertEquals(0, results.size());
570    }
571  }
572
573
574  @Test
575  public void testDeleteVersionSameTimestamp() throws IOException {
576    KeyValue [] kvs = new KeyValue [] {
577        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
578        create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"),
579    };
580    List<KeyValueScanner> scanners = scanFixture(kvs);
581    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
582    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
583      List<Cell> results = new ArrayList<>();
584      assertFalse(scan.next(results));
585      assertEquals(0, results.size());
586    }
587  }
588
589  /*
590   * Test the case where there is a delete row 'in front of' the next row, the scanner
591   * will move to the next row.
592   */
593  @Test
594  public void testDeletedRowThenGoodRow() throws IOException {
595    KeyValue [] kvs = new KeyValue [] {
596        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
597        create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"),
598        create("R2", "cf", "a", 20, KeyValue.Type.Put, "dont-care")
599    };
600    List<KeyValueScanner> scanners = scanFixture(kvs);
601    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
602    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
603      List<Cell> results = new ArrayList<>();
604      assertEquals(true, scan.next(results));
605      assertEquals(0, results.size());
606
607      assertEquals(true, scan.next(results));
608      assertEquals(1, results.size());
609      assertEquals(kvs[2], results.get(0));
610
611      assertEquals(false, scan.next(results));
612    }
613  }
614
615  @Test
616  public void testDeleteVersionMaskingMultiplePuts() throws IOException {
617    long now = System.currentTimeMillis();
618    KeyValue [] kvs1 = new KeyValue[] {
619        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
620        create("R1", "cf", "a", now, KeyValue.Type.Delete, "dont-care")
621    };
622    KeyValue [] kvs2 = new KeyValue[] {
623        create("R1", "cf", "a", now-500, KeyValue.Type.Put, "dont-care"),
624        create("R1", "cf", "a", now-100, KeyValue.Type.Put, "dont-care"),
625        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care")
626    };
627    List<KeyValueScanner> scanners = scanFixture(kvs1, kvs2);
628
629    try (StoreScanner scan = new StoreScanner(new Scan().withStartRow(Bytes.toBytes("R1")),
630        scanInfo, getCols("a"), scanners)) {
631      List<Cell> results = new ArrayList<>();
632      // the two put at ts=now will be masked by the 1 delete, and
633      // since the scan default returns 1 version we'll return the newest
634      // key, which is kvs[2], now-100.
635      assertEquals(true, scan.next(results));
636      assertEquals(1, results.size());
637      assertEquals(kvs2[1], results.get(0));
638    }
639  }
640
641  @Test
642  public void testDeleteVersionsMixedAndMultipleVersionReturn() throws IOException {
643    long now = System.currentTimeMillis();
644    KeyValue [] kvs1 = new KeyValue[] {
645        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
646        create("R1", "cf", "a", now, KeyValue.Type.Delete, "dont-care")
647    };
648    KeyValue [] kvs2 = new KeyValue[] {
649        create("R1", "cf", "a", now-500, KeyValue.Type.Put, "dont-care"),
650        create("R1", "cf", "a", now+500, KeyValue.Type.Put, "dont-care"),
651        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
652        create("R2", "cf", "z", now, KeyValue.Type.Put, "dont-care")
653    };
654    List<KeyValueScanner> scanners = scanFixture(kvs1, kvs2);
655
656    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1")).readVersions(2);
657    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
658      List<Cell> results = new ArrayList<>();
659      assertEquals(true, scan.next(results));
660      assertEquals(2, results.size());
661      assertEquals(kvs2[1], results.get(0));
662      assertEquals(kvs2[0], results.get(1));
663    }
664  }
665
666  @Test
667  public void testWildCardOneVersionScan() throws IOException {
668    KeyValue [] kvs = new KeyValue [] {
669        create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
670        create("R1", "cf", "b", 1, KeyValue.Type.Put, "dont-care"),
671        create("R1", "cf", "a", 1, KeyValue.Type.DeleteColumn, "dont-care"),
672    };
673    List<KeyValueScanner> scanners = scanFixture(kvs);
674    try (StoreScanner scan =
675        new StoreScanner(new Scan().withStartRow(Bytes.toBytes("R1")), scanInfo, null, scanners)) {
676      List<Cell> results = new ArrayList<>();
677      assertEquals(true, scan.next(results));
678      assertEquals(2, results.size());
679      assertEquals(kvs[0], results.get(0));
680      assertEquals(kvs[1], results.get(1));
681    }
682  }
683
684  @Test
685  public void testWildCardScannerUnderDeletes() throws IOException {
686    KeyValue [] kvs = new KeyValue [] {
687        create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"), // inc
688        // orphaned delete column.
689        create("R1", "cf", "a", 1, KeyValue.Type.DeleteColumn, "dont-care"),
690        // column b
691        create("R1", "cf", "b", 2, KeyValue.Type.Put, "dont-care"), // inc
692        create("R1", "cf", "b", 1, KeyValue.Type.Put, "dont-care"), // inc
693        // column c
694        create("R1", "cf", "c", 10, KeyValue.Type.Delete, "dont-care"),
695        create("R1", "cf", "c", 10, KeyValue.Type.Put, "dont-care"), // no
696        create("R1", "cf", "c", 9, KeyValue.Type.Put, "dont-care"),  // inc
697        // column d
698        create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"), // inc
699        create("R1", "cf", "d", 10, KeyValue.Type.DeleteColumn, "dont-care"),
700        create("R1", "cf", "d", 9, KeyValue.Type.Put, "dont-care"),  // no
701        create("R1", "cf", "d", 8, KeyValue.Type.Put, "dont-care"),  // no
702
703    };
704    List<KeyValueScanner> scanners = scanFixture(kvs);
705    try (StoreScanner scan =
706        new StoreScanner(new Scan().readVersions(2), scanInfo, null, scanners)) {
707      List<Cell> results = new ArrayList<>();
708      assertEquals(true, scan.next(results));
709      assertEquals(5, results.size());
710      assertEquals(kvs[0], results.get(0));
711      assertEquals(kvs[2], results.get(1));
712      assertEquals(kvs[3], results.get(2));
713      assertEquals(kvs[6], results.get(3));
714      assertEquals(kvs[7], results.get(4));
715    }
716  }
717
718  @Test
719  public void testDeleteFamily() throws IOException {
720    KeyValue[] kvs = new KeyValue[] {
721        create("R1", "cf", "a", 100, KeyValue.Type.DeleteFamily, "dont-care"),
722        create("R1", "cf", "b", 11, KeyValue.Type.Put, "dont-care"),
723        create("R1", "cf", "c", 11, KeyValue.Type.Put, "dont-care"),
724        create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
725        create("R1", "cf", "e", 11, KeyValue.Type.Put, "dont-care"),
726        create("R1", "cf", "e", 11, KeyValue.Type.DeleteColumn, "dont-care"),
727        create("R1", "cf", "f", 11, KeyValue.Type.Put, "dont-care"),
728        create("R1", "cf", "g", 11, KeyValue.Type.Put, "dont-care"),
729        create("R1", "cf", "g", 11, KeyValue.Type.Delete, "dont-care"),
730        create("R1", "cf", "h", 11, KeyValue.Type.Put, "dont-care"),
731        create("R1", "cf", "i", 11, KeyValue.Type.Put, "dont-care"),
732        create("R2", "cf", "a", 11, KeyValue.Type.Put, "dont-care"),
733    };
734    List<KeyValueScanner> scanners = scanFixture(kvs);
735    try (StoreScanner scan =
736        new StoreScanner(new Scan().readAllVersions(), scanInfo, null, scanners)) {
737      List<Cell> results = new ArrayList<>();
738      assertEquals(true, scan.next(results));
739      assertEquals(0, results.size());
740      assertEquals(true, scan.next(results));
741      assertEquals(1, results.size());
742      assertEquals(kvs[kvs.length - 1], results.get(0));
743
744      assertEquals(false, scan.next(results));
745    }
746  }
747
748  @Test
749  public void testDeleteColumn() throws IOException {
750    KeyValue [] kvs = new KeyValue[] {
751        create("R1", "cf", "a", 10, KeyValue.Type.DeleteColumn, "dont-care"),
752        create("R1", "cf", "a", 9, KeyValue.Type.Delete, "dont-care"),
753        create("R1", "cf", "a", 8, KeyValue.Type.Put, "dont-care"),
754        create("R1", "cf", "b", 5, KeyValue.Type.Put, "dont-care")
755    };
756    List<KeyValueScanner> scanners = scanFixture(kvs);
757    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, null, scanners)) {
758      List<Cell> results = new ArrayList<>();
759      assertEquals(true, scan.next(results));
760      assertEquals(1, results.size());
761      assertEquals(kvs[3], results.get(0));
762    }
763  }
764
765  private static final KeyValue[] kvs = new KeyValue[] {
766        create("R1", "cf", "a", 11, KeyValue.Type.Put, "dont-care"),
767        create("R1", "cf", "b", 11, KeyValue.Type.Put, "dont-care"),
768        create("R1", "cf", "c", 11, KeyValue.Type.Put, "dont-care"),
769        create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
770        create("R1", "cf", "e", 11, KeyValue.Type.Put, "dont-care"),
771        create("R1", "cf", "f", 11, KeyValue.Type.Put, "dont-care"),
772        create("R1", "cf", "g", 11, KeyValue.Type.Put, "dont-care"),
773        create("R1", "cf", "h", 11, KeyValue.Type.Put, "dont-care"),
774        create("R1", "cf", "i", 11, KeyValue.Type.Put, "dont-care"),
775        create("R2", "cf", "a", 11, KeyValue.Type.Put, "dont-care"),
776    };
777
778  @Test
779  public void testSkipColumn() throws IOException {
780    List<KeyValueScanner> scanners = scanFixture(kvs);
781    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, getCols("a", "d"), scanners)) {
782      List<Cell> results = new ArrayList<>();
783      assertEquals(true, scan.next(results));
784      assertEquals(2, results.size());
785      assertEquals(kvs[0], results.get(0));
786      assertEquals(kvs[3], results.get(1));
787      results.clear();
788
789      assertEquals(true, scan.next(results));
790      assertEquals(1, results.size());
791      assertEquals(kvs[kvs.length - 1], results.get(0));
792
793      results.clear();
794      assertEquals(false, scan.next(results));
795    }
796  }
797
798  /*
799   * Test expiration of KeyValues in combination with a configured TTL for
800   * a column family (as should be triggered in a major compaction).
801   */
802  @Test
803  public void testWildCardTtlScan() throws IOException {
804    long now = System.currentTimeMillis();
805    KeyValue [] kvs = new KeyValue[] {
806        create("R1", "cf", "a", now-1000, KeyValue.Type.Put, "dont-care"),
807        create("R1", "cf", "b", now-10, KeyValue.Type.Put, "dont-care"),
808        create("R1", "cf", "c", now-200, KeyValue.Type.Put, "dont-care"),
809        create("R1", "cf", "d", now-10000, KeyValue.Type.Put, "dont-care"),
810        create("R2", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
811        create("R2", "cf", "b", now-10, KeyValue.Type.Put, "dont-care"),
812        create("R2", "cf", "c", now-200, KeyValue.Type.Put, "dont-care"),
813        create("R2", "cf", "c", now-1000, KeyValue.Type.Put, "dont-care")
814    };
815    List<KeyValueScanner> scanners = scanFixture(kvs);
816    Scan scan = new Scan();
817    scan.readVersions(1);
818    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
819        HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
820    try (StoreScanner scanner = new StoreScanner(scan, scanInfo, null, scanners)) {
821      List<Cell> results = new ArrayList<>();
822      assertEquals(true, scanner.next(results));
823      assertEquals(2, results.size());
824      assertEquals(kvs[1], results.get(0));
825      assertEquals(kvs[2], results.get(1));
826      results.clear();
827
828      assertEquals(true, scanner.next(results));
829      assertEquals(3, results.size());
830      assertEquals(kvs[4], results.get(0));
831      assertEquals(kvs[5], results.get(1));
832      assertEquals(kvs[6], results.get(2));
833      results.clear();
834
835      assertEquals(false, scanner.next(results));
836    }
837  }
838
839  @Test
840  public void testScannerReseekDoesntNPE() throws Exception {
841    List<KeyValueScanner> scanners = scanFixture(kvs);
842    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, getCols("a", "d"), scanners)) {
843      // Previously a updateReaders twice in a row would cause an NPE. In test this would also
844      // normally cause an NPE because scan.store is null. So as long as we get through these
845      // two calls we are good and the bug was quashed.
846      scan.updateReaders(Collections.emptyList(), Collections.emptyList());
847      scan.updateReaders(Collections.emptyList(), Collections.emptyList());
848      scan.peek();
849    }
850  }
851
852  @Test @Ignore("this fails, since we don't handle deletions, etc, in peek")
853  public void testPeek() throws Exception {
854    KeyValue[] kvs = new KeyValue [] {
855        create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
856        create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"),
857    };
858    List<KeyValueScanner> scanners = scanFixture(kvs);
859    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
860    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
861      assertNull(scan.peek());
862    }
863  }
864
865  /**
866   * Ensure that expired delete family markers don't override valid puts
867   */
868  @Test
869  public void testExpiredDeleteFamily() throws Exception {
870    long now = System.currentTimeMillis();
871    KeyValue[] kvs = new KeyValue[] {
872        new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null, now-1000,
873            KeyValue.Type.DeleteFamily),
874        create("R1", "cf", "a", now-10, KeyValue.Type.Put,
875            "dont-care"),
876    };
877    List<KeyValueScanner> scanners = scanFixture(kvs);
878    Scan scan = new Scan();
879    scan.readVersions(1);
880    // scanner with ttl equal to 500
881    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
882        HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
883    try (StoreScanner scanner = new StoreScanner(scan, scanInfo, null, scanners)) {
884      List<Cell> results = new ArrayList<>();
885      assertEquals(true, scanner.next(results));
886      assertEquals(1, results.size());
887      assertEquals(kvs[1], results.get(0));
888      results.clear();
889
890      assertEquals(false, scanner.next(results));
891    }
892  }
893
894  @Test
895  public void testDeleteMarkerLongevity() throws Exception {
896    try {
897      final long now = System.currentTimeMillis();
898      EnvironmentEdgeManagerTestHelper.injectEdge(new EnvironmentEdge() {
899        @Override
900        public long currentTime() {
901          return now;
902        }
903      });
904      KeyValue[] kvs = new KeyValue[]{
905        /*0*/ new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null,
906        now - 100, KeyValue.Type.DeleteFamily), // live
907        /*1*/ new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null,
908        now - 1000, KeyValue.Type.DeleteFamily), // expired
909        /*2*/ create("R1", "cf", "a", now - 50,
910        KeyValue.Type.Put, "v3"), // live
911        /*3*/ create("R1", "cf", "a", now - 55,
912        KeyValue.Type.Delete, "dontcare"), // live
913        /*4*/ create("R1", "cf", "a", now - 55,
914        KeyValue.Type.Put, "deleted-version v2"), // deleted
915        /*5*/ create("R1", "cf", "a", now - 60,
916        KeyValue.Type.Put, "v1"), // live
917        /*6*/ create("R1", "cf", "a", now - 65,
918        KeyValue.Type.Put, "v0"), // max-version reached
919        /*7*/ create("R1", "cf", "a",
920        now - 100, KeyValue.Type.DeleteColumn, "dont-care"), // max-version
921        /*8*/ create("R1", "cf", "b", now - 600,
922        KeyValue.Type.DeleteColumn, "dont-care"), //expired
923        /*9*/ create("R1", "cf", "b", now - 70,
924        KeyValue.Type.Put, "v2"), //live
925        /*10*/ create("R1", "cf", "b", now - 750,
926        KeyValue.Type.Put, "v1"), //expired
927        /*11*/ create("R1", "cf", "c", now - 500,
928        KeyValue.Type.Delete, "dontcare"), //expired
929        /*12*/ create("R1", "cf", "c", now - 600,
930        KeyValue.Type.Put, "v1"), //expired
931        /*13*/ create("R1", "cf", "c", now - 1000,
932        KeyValue.Type.Delete, "dontcare"), //expired
933        /*14*/ create("R1", "cf", "d", now - 60,
934        KeyValue.Type.Put, "expired put"), //live
935        /*15*/ create("R1", "cf", "d", now - 100,
936        KeyValue.Type.Delete, "not-expired delete"), //live
937      };
938      List<KeyValueScanner> scanners = scanFixture(kvs);
939      ScanInfo scanInfo = new ScanInfo(CONF, Bytes.toBytes("cf"),
940        0 /* minVersions */,
941        2 /* maxVersions */, 500 /* ttl */,
942        KeepDeletedCells.FALSE /* keepDeletedCells */,
943        HConstants.DEFAULT_BLOCKSIZE /* block size */,
944        200, /* timeToPurgeDeletes */
945        CellComparator.getInstance(), false);
946      try (StoreScanner scanner =
947          new StoreScanner(scanInfo, OptionalInt.of(2), ScanType.COMPACT_DROP_DELETES, scanners)) {
948        List<Cell> results = new ArrayList<>();
949        results = new ArrayList<>();
950        assertEquals(true, scanner.next(results));
951        assertEquals(kvs[0], results.get(0));
952        assertEquals(kvs[2], results.get(1));
953        assertEquals(kvs[3], results.get(2));
954        assertEquals(kvs[5], results.get(3));
955        assertEquals(kvs[9], results.get(4));
956        assertEquals(kvs[14], results.get(5));
957        assertEquals(kvs[15], results.get(6));
958        assertEquals(7, results.size());
959      }
960    } finally {
961      EnvironmentEdgeManagerTestHelper.reset();
962    }
963  }
964
965  @Test
966  public void testPreadNotEnabledForCompactionStoreScanners() throws Exception {
967    long now = System.currentTimeMillis();
968    KeyValue[] kvs = new KeyValue[] {
969        new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null, now - 1000,
970            KeyValue.Type.DeleteFamily),
971        create("R1", "cf", "a", now - 10, KeyValue.Type.Put, "dont-care"), };
972    List<KeyValueScanner> scanners = scanFixture(kvs);
973    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
974        HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
975    try (StoreScanner storeScanner = new StoreScanner(scanInfo, OptionalInt.empty(),
976        ScanType.COMPACT_RETAIN_DELETES, scanners)) {
977      assertFalse(storeScanner.isScanUsePread());
978    }
979  }
980}