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