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