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.KeyValueTestUtil.create;
021import static org.apache.hadoop.hbase.regionserver.KeyValueScanFixture.scanFixture;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertNotNull;
025import static org.junit.Assert.assertNull;
026import static org.junit.Assert.assertSame;
027import static org.junit.Assert.assertTrue;
028
029import java.io.IOException;
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.Collections;
033import java.util.List;
034import java.util.NavigableSet;
035import java.util.Set;
036import java.util.TreeSet;
037import java.util.concurrent.atomic.AtomicInteger;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.fs.FileSystem;
040import org.apache.hadoop.fs.Path;
041import org.apache.hadoop.hbase.Cell;
042import org.apache.hadoop.hbase.CellBuilderType;
043import org.apache.hadoop.hbase.CellComparator;
044import org.apache.hadoop.hbase.CellUtil;
045import org.apache.hadoop.hbase.CompareOperator;
046import org.apache.hadoop.hbase.ExtendedCell;
047import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
048import org.apache.hadoop.hbase.HBaseClassTestRule;
049import org.apache.hadoop.hbase.HBaseConfiguration;
050import org.apache.hadoop.hbase.HBaseTestingUtil;
051import org.apache.hadoop.hbase.HConstants;
052import org.apache.hadoop.hbase.KeepDeletedCells;
053import org.apache.hadoop.hbase.KeyValue;
054import org.apache.hadoop.hbase.PrivateCellUtil;
055import org.apache.hadoop.hbase.TableName;
056import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
057import org.apache.hadoop.hbase.client.Get;
058import org.apache.hadoop.hbase.client.RegionInfo;
059import org.apache.hadoop.hbase.client.RegionInfoBuilder;
060import org.apache.hadoop.hbase.client.Scan;
061import org.apache.hadoop.hbase.filter.BinaryComparator;
062import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
063import org.apache.hadoop.hbase.filter.Filter;
064import org.apache.hadoop.hbase.filter.QualifierFilter;
065import org.apache.hadoop.hbase.io.hfile.CacheConfig;
066import org.apache.hadoop.hbase.io.hfile.HFileContext;
067import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
068import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
069import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
070import org.apache.hadoop.hbase.testclassification.RegionServerTests;
071import org.apache.hadoop.hbase.testclassification.SmallTests;
072import org.apache.hadoop.hbase.util.Bytes;
073import org.apache.hadoop.hbase.util.CollectionBackedScanner;
074import org.apache.hadoop.hbase.util.EnvironmentEdge;
075import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
076import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
077import org.junit.ClassRule;
078import org.junit.Ignore;
079import org.junit.Rule;
080import org.junit.Test;
081import org.junit.experimental.categories.Category;
082import org.junit.rules.TestName;
083import org.mockito.Mockito;
084import org.slf4j.Logger;
085import org.slf4j.LoggerFactory;
086
087// Can't be small as it plays with EnvironmentEdgeManager
088@Category({ RegionServerTests.class, SmallTests.class })
089public class TestStoreScanner {
090
091  @ClassRule
092  public static final HBaseClassTestRule CLASS_RULE =
093    HBaseClassTestRule.forClass(TestStoreScanner.class);
094
095  private static final Logger LOG = LoggerFactory.getLogger(TestStoreScanner.class);
096  @Rule
097  public TestName name = new TestName();
098  private static final String CF_STR = "cf";
099  private static final byte[] CF = Bytes.toBytes(CF_STR);
100  static Configuration CONF = HBaseConfiguration.create();
101  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
102  private ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, Integer.MAX_VALUE, Long.MAX_VALUE,
103    KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
104
105  /**
106   * From here on down, we have a bunch of defines and specific CELL_GRID of Cells. The CELL_GRID
107   * then has a Scanner that can fake out 'block' transitions. All this elaborate setup is for tests
108   * that ensure we don't overread, and that the {@link StoreScanner} is not overly enthusiastic.
109   */
110  private static final byte[] ZERO = new byte[] { '0' };
111  private static final byte[] ZERO_POINT_ZERO = new byte[] { '0', '.', '0' };
112  private static final byte[] ONE = new byte[] { '1' };
113  private static final byte[] TWO = new byte[] { '2' };
114  private static final byte[] TWO_POINT_TWO = new byte[] { '2', '.', '2' };
115  private static final byte[] THREE = new byte[] { '3' };
116  private static final byte[] FOUR = new byte[] { '4' };
117  private static final byte[] FIVE = new byte[] { '5' };
118  private static final byte[] VALUE = new byte[] { 'v' };
119  private static final int CELL_GRID_BLOCK2_BOUNDARY = 4;
120  private static final int CELL_GRID_BLOCK3_BOUNDARY = 11;
121  private static final int CELL_GRID_BLOCK4_BOUNDARY = 15;
122  private static final int CELL_GRID_BLOCK5_BOUNDARY = 19;
123
124  /**
125   * Five rows by four columns distinguished by column qualifier (column qualifier is one of the
126   * four rows... ONE, TWO, etc.). Exceptions are a weird row after TWO; it is TWO_POINT_TWO. And
127   * then row FOUR has five columns finishing w/ row FIVE having a single column. We will use this
128   * to test scan does the right thing as it we do Gets, StoreScanner#optimize, and what we do on
129   * (faked) block boundaries.
130   */
131  private static final ExtendedCell[] CELL_GRID = new ExtendedCell[] {
132    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
133      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
134      .build(),
135    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
136      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
137      .build(),
138    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
139      .setQualifier(THREE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
140      .build(),
141    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
142      .setQualifier(FOUR).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
143      .build(),
144    // Offset 4 CELL_GRID_BLOCK2_BOUNDARY
145    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
146      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
147      .build(),
148    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
149      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
150      .build(),
151    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
152      .setQualifier(THREE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
153      .build(),
154    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
155      .setQualifier(FOUR).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
156      .build(),
157    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO_POINT_TWO).setFamily(CF)
158      .setQualifier(ZERO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
159      .build(),
160    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO_POINT_TWO).setFamily(CF)
161      .setQualifier(ZERO_POINT_ZERO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode())
162      .setValue(VALUE).build(),
163    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO_POINT_TWO).setFamily(CF)
164      .setQualifier(FIVE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
165      .build(),
166    // Offset 11! CELL_GRID_BLOCK3_BOUNDARY
167    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(THREE).setFamily(CF)
168      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
169      .build(),
170    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(THREE).setFamily(CF)
171      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
172      .build(),
173    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(THREE).setFamily(CF)
174      .setQualifier(THREE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
175      .build(),
176    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(THREE).setFamily(CF)
177      .setQualifier(FOUR).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
178      .build(),
179    // Offset 15 CELL_GRID_BLOCK4_BOUNDARY
180    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FOUR).setFamily(CF)
181      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
182      .build(),
183    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FOUR).setFamily(CF)
184      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
185      .build(),
186    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FOUR).setFamily(CF)
187      .setQualifier(THREE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
188      .build(),
189    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FOUR).setFamily(CF)
190      .setQualifier(FOUR).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
191      .build(),
192    // Offset 19 CELL_GRID_BLOCK5_BOUNDARY
193    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FOUR).setFamily(CF)
194      .setQualifier(FIVE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
195      .build(),
196    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(FIVE).setFamily(CF)
197      .setQualifier(ZERO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
198      .build(), };
199
200  private static class KeyValueHeapWithCount extends KeyValueHeap {
201
202    final AtomicInteger count;
203
204    public KeyValueHeapWithCount(List<? extends KeyValueScanner> scanners,
205      CellComparator comparator, AtomicInteger count) throws IOException {
206      super(scanners, comparator);
207      this.count = count;
208    }
209
210    @Override
211    public ExtendedCell peek() {
212      this.count.incrementAndGet();
213      return super.peek();
214    }
215  }
216
217  /**
218   * A StoreScanner for our CELL_GRID above. Fakes the block transitions. Does counts of calls to
219   * optimize and counts of when optimize actually did an optimize.
220   */
221  private static class CellGridStoreScanner extends StoreScanner {
222    // Count of how often optimize is called and of how often it does an optimize.
223    AtomicInteger count;
224    final AtomicInteger optimization = new AtomicInteger(0);
225
226    CellGridStoreScanner(final Scan scan, ScanInfo scanInfo) throws IOException {
227      super(scan, scanInfo, scan.getFamilyMap().get(CF),
228        Arrays.<KeyValueScanner> asList(new KeyValueScanner[] {
229          new KeyValueScanFixture(CellComparator.getInstance(), CELL_GRID) }));
230    }
231
232    @Override
233    protected void resetKVHeap(List<? extends KeyValueScanner> scanners, CellComparator comparator)
234      throws IOException {
235      if (count == null) {
236        count = new AtomicInteger(0);
237      }
238      heap = newKVHeap(scanners, comparator);
239    }
240
241    @Override
242    protected KeyValueHeap newKVHeap(List<? extends KeyValueScanner> scanners,
243      CellComparator comparator) throws IOException {
244      return new KeyValueHeapWithCount(scanners, comparator, count);
245    }
246
247    @Override
248    protected boolean trySkipToNextRow(ExtendedCell cell) throws IOException {
249      boolean optimized = super.trySkipToNextRow(cell);
250      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
251        + ", optimized=" + optimized);
252      if (optimized) {
253        optimization.incrementAndGet();
254      }
255      return optimized;
256    }
257
258    @Override
259    protected boolean trySkipToNextColumn(ExtendedCell cell) throws IOException {
260      boolean optimized = super.trySkipToNextColumn(cell);
261      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
262        + ", optimized=" + optimized);
263      if (optimized) {
264        optimization.incrementAndGet();
265      }
266      return optimized;
267    }
268
269    @Override
270    public ExtendedCell getNextIndexedKey() {
271      // Fake block boundaries by having index of next block change as we go through scan.
272      return count.get() > CELL_GRID_BLOCK4_BOUNDARY
273        ? PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK5_BOUNDARY])
274        : count.get() > CELL_GRID_BLOCK3_BOUNDARY
275          ? PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK4_BOUNDARY])
276        : count.get() > CELL_GRID_BLOCK2_BOUNDARY
277          ? PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK3_BOUNDARY])
278        : PrivateCellUtil.createFirstOnRow(CELL_GRID[CELL_GRID_BLOCK2_BOUNDARY]);
279    }
280  }
281
282  private static final int CELL_WITH_VERSIONS_BLOCK2_BOUNDARY = 4;
283
284  private static final ExtendedCell[] CELL_WITH_VERSIONS = new ExtendedCell[] {
285    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
286      .setQualifier(ONE).setTimestamp(2L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
287      .build(),
288    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
289      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
290      .build(),
291    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
292      .setQualifier(TWO).setTimestamp(2L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
293      .build(),
294    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(ONE).setFamily(CF)
295      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
296      .build(),
297    // Offset 4 CELL_WITH_VERSIONS_BLOCK2_BOUNDARY
298    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
299      .setQualifier(ONE).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
300      .build(),
301    ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(TWO).setFamily(CF)
302      .setQualifier(TWO).setTimestamp(1L).setType(KeyValue.Type.Put.getCode()).setValue(VALUE)
303      .build(), };
304
305  private static class CellWithVersionsStoreScanner extends StoreScanner {
306    // Count of how often optimize is called and of how often it does an optimize.
307    final AtomicInteger optimization = new AtomicInteger(0);
308
309    CellWithVersionsStoreScanner(final Scan scan, ScanInfo scanInfo) throws IOException {
310      super(scan, scanInfo, scan.getFamilyMap().get(CF),
311        Arrays.<KeyValueScanner> asList(new KeyValueScanner[] {
312          new KeyValueScanFixture(CellComparator.getInstance(), CELL_WITH_VERSIONS) }));
313    }
314
315    @Override
316    protected boolean trySkipToNextColumn(ExtendedCell cell) throws IOException {
317      boolean optimized = super.trySkipToNextColumn(cell);
318      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
319        + ", optimized=" + optimized);
320      if (optimized) {
321        optimization.incrementAndGet();
322      }
323      return optimized;
324    }
325
326    @Override
327    public ExtendedCell getNextIndexedKey() {
328      // Fake block boundaries by having index of next block change as we go through scan.
329      return PrivateCellUtil
330        .createFirstOnRow(CELL_WITH_VERSIONS[CELL_WITH_VERSIONS_BLOCK2_BOUNDARY]);
331    }
332  }
333
334  private static class CellWithVersionsNoOptimizeStoreScanner extends StoreScanner {
335    // Count of how often optimize is called and of how often it does an optimize.
336    final AtomicInteger optimization = new AtomicInteger(0);
337
338    CellWithVersionsNoOptimizeStoreScanner(Scan scan, ScanInfo scanInfo) throws IOException {
339      super(scan, scanInfo, scan.getFamilyMap().get(CF),
340        Arrays.<KeyValueScanner> asList(new KeyValueScanner[] {
341          new KeyValueScanFixture(CellComparator.getInstance(), CELL_WITH_VERSIONS) }));
342    }
343
344    @Override
345    protected boolean trySkipToNextColumn(ExtendedCell cell) throws IOException {
346      boolean optimized = super.trySkipToNextColumn(cell);
347      LOG.info("Cell=" + cell + ", nextIndex=" + CellUtil.toString(getNextIndexedKey(), false)
348        + ", optimized=" + optimized);
349      if (optimized) {
350        optimization.incrementAndGet();
351      }
352      return optimized;
353    }
354
355    @Override
356    public ExtendedCell getNextIndexedKey() {
357      return null;
358    }
359  }
360
361  @Test
362  public void testWithColumnCountGetFilter() throws Exception {
363    Get get = new Get(ONE);
364    get.readAllVersions();
365    get.addFamily(CF);
366    get.setFilter(new ColumnCountGetFilter(2));
367
368    try (CellWithVersionsNoOptimizeStoreScanner scannerNoOptimize =
369      new CellWithVersionsNoOptimizeStoreScanner(new Scan(get), this.scanInfo)) {
370      List<Cell> results = new ArrayList<>();
371      while (scannerNoOptimize.next(results)) {
372        continue;
373      }
374      assertEquals(2, results.size());
375      assertTrue(CellUtil.matchingColumn(results.get(0), CELL_WITH_VERSIONS[0]));
376      assertTrue(CellUtil.matchingColumn(results.get(1), CELL_WITH_VERSIONS[2]));
377      assertTrue("Optimize should do some optimizations",
378        scannerNoOptimize.optimization.get() == 0);
379    }
380
381    get.setFilter(new ColumnCountGetFilter(2));
382    try (CellWithVersionsStoreScanner scanner =
383      new CellWithVersionsStoreScanner(new Scan(get), this.scanInfo)) {
384      List<Cell> results = new ArrayList<>();
385      while (scanner.next(results)) {
386        continue;
387      }
388      assertEquals(2, results.size());
389      assertTrue(CellUtil.matchingColumn(results.get(0), CELL_WITH_VERSIONS[0]));
390      assertTrue(CellUtil.matchingColumn(results.get(1), CELL_WITH_VERSIONS[2]));
391      assertTrue("Optimize should do some optimizations", scanner.optimization.get() > 0);
392    }
393  }
394
395  /*
396   * Test utility for building a NavigableSet for scanners.
397   */
398  NavigableSet<byte[]> getCols(String... strCols) {
399    NavigableSet<byte[]> cols = new TreeSet<>(Bytes.BYTES_COMPARATOR);
400    for (String col : strCols) {
401      byte[] bytes = Bytes.toBytes(col);
402      cols.add(bytes);
403    }
404    return cols;
405  }
406
407  @Test
408  public void testFullRowGetDoesNotOverreadWhenRowInsideOneBlock() throws IOException {
409    // Do a Get against row two. Row two is inside a block that starts with row TWO but ends with
410    // row TWO_POINT_TWO. We should read one block only.
411    Get get = new Get(TWO);
412    Scan scan = new Scan(get);
413    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
414      List<Cell> results = new ArrayList<>();
415      while (scanner.next(results)) {
416        continue;
417      }
418      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
419      // TWO_POINT_TWO row does not have a a column ONE.
420      assertEquals(4, results.size());
421      // We should have gone the optimize route 5 times totally... an INCLUDE for the four cells
422      // in the row plus the DONE on the end.
423      assertEquals(5, scanner.count.get());
424      assertEquals(1, scanner.memstoreOnlyReads);
425      // For a full row Get, there should be no opportunity for scanner optimization.
426      assertEquals(0, scanner.optimization.get());
427    }
428  }
429
430  @Test
431  public void testFullRowSpansBlocks() throws IOException {
432    // Do a Get against row FOUR. It spans two blocks.
433    Get get = new Get(FOUR);
434    Scan scan = new Scan(get);
435    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
436      List<Cell> results = new ArrayList<>();
437      while (scanner.next(results)) {
438        continue;
439      }
440      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
441      // TWO_POINT_TWO row does not have a a column ONE.
442      assertEquals(5, results.size());
443      // We should have gone the optimize route 6 times totally... an INCLUDE for the five cells
444      // in the row plus the DONE on the end.
445      assertEquals(6, scanner.count.get());
446      // For a full row Get, there should be no opportunity for scanner optimization.
447      assertEquals(0, scanner.optimization.get());
448    }
449  }
450
451  /**
452   * Test optimize in StoreScanner. Test that we skip to the next 'block' when we it makes sense
453   * reading the block 'index'.
454   */
455  @Test
456  public void testOptimize() throws IOException {
457    Scan scan = new Scan();
458    // A scan that just gets the first qualifier on each row of the CELL_GRID
459    scan.addColumn(CF, ONE);
460    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
461      List<Cell> results = new ArrayList<>();
462      while (scanner.next(results)) {
463        continue;
464      }
465      // Should be four results of column 1 (though there are 5 rows in the CELL_GRID -- the
466      // TWO_POINT_TWO row does not have a a column ONE.
467      assertEquals(4, results.size());
468      for (Cell cell : results) {
469        assertTrue(Bytes.equals(ONE, 0, ONE.length, cell.getQualifierArray(),
470          cell.getQualifierOffset(), cell.getQualifierLength()));
471      }
472      assertTrue("Optimize should do some optimizations", scanner.optimization.get() > 0);
473    }
474  }
475
476  /**
477   * Ensure the optimize Scan method in StoreScanner does not get in the way of a Get doing minimum
478   * work... seeking to start of block and then SKIPPING until we find the wanted Cell. This
479   * 'simple' scenario mimics case of all Cells fitting inside a single HFileBlock. See HBASE-15392.
480   * This test is a little cryptic. Takes a bit of staring to figure what it up to.
481   */
482  @Test
483  public void testOptimizeAndGet() throws IOException {
484    // First test a Get of two columns in the row R2. Every Get is a Scan. Get columns named
485    // R2 and R3.
486    Get get = new Get(TWO);
487    get.addColumn(CF, TWO);
488    get.addColumn(CF, THREE);
489    Scan scan = new Scan(get);
490    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
491      List<Cell> results = new ArrayList<>();
492      // For a Get there should be no more next's after the first call.
493      assertEquals(false, scanner.next(results));
494      // Should be one result only.
495      assertEquals(2, results.size());
496      // And we should have gone through optimize twice only.
497      assertEquals("First qcode is SEEK_NEXT_COL and second INCLUDE_AND_SEEK_NEXT_ROW", 3,
498        scanner.count.get());
499      assertEquals("Memstore Read count should be", 1, scanner.memstoreOnlyReads);
500    }
501  }
502
503  /**
504   * Ensure that optimize does not cause the Get to do more seeking than required. Optimize (see
505   * HBASE-15392) was causing us to seek all Cells in a block when a Get Scan if the next block
506   * index/start key was a different row to the current one. A bug. We'd call next too often because
507   * we had to exhaust all Cells in the current row making us load the next block just to discard
508   * what we read there. This test is a little cryptic. Takes a bit of staring to figure what it up
509   * to.
510   */
511  @Test
512  public void testOptimizeAndGetWithFakedNextBlockIndexStart() throws IOException {
513    // First test a Get of second column in the row R2. Every Get is a Scan. Second column has a
514    // qualifier of R2.
515    Get get = new Get(THREE);
516    get.addColumn(CF, TWO);
517    Scan scan = new Scan(get);
518    try (CellGridStoreScanner scanner = new CellGridStoreScanner(scan, this.scanInfo)) {
519      List<Cell> results = new ArrayList<>();
520      // For a Get there should be no more next's after the first call.
521      assertEquals(false, scanner.next(results));
522      // Should be one result only.
523      assertEquals(1, results.size());
524      // And we should have gone through optimize twice only.
525      assertEquals("First qcode is SEEK_NEXT_COL and second INCLUDE_AND_SEEK_NEXT_ROW", 2,
526        scanner.count.get());
527    }
528  }
529
530  @Test
531  public void testScanTimeRange() throws IOException {
532    String r1 = "R1";
533    // returns only 1 of these 2 even though same timestamp
534    KeyValue[] kvs = new KeyValue[] { create(r1, CF_STR, "a", 1, KeyValue.Type.Put, "dont-care"),
535      create(r1, CF_STR, "a", 2, KeyValue.Type.Put, "dont-care"),
536      create(r1, CF_STR, "a", 3, KeyValue.Type.Put, "dont-care"),
537      create(r1, CF_STR, "a", 4, KeyValue.Type.Put, "dont-care"),
538      create(r1, CF_STR, "a", 5, KeyValue.Type.Put, "dont-care"), };
539    List<KeyValueScanner> scanners = Arrays.<KeyValueScanner> asList(
540      new KeyValueScanner[] { new KeyValueScanFixture(CellComparator.getInstance(), kvs) });
541    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
542    scanSpec.setTimeRange(0, 6);
543    scanSpec.readAllVersions();
544    List<Cell> results = null;
545    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
546      results = new ArrayList<>();
547      assertEquals(true, scan.next(results));
548      assertEquals(5, results.size());
549      assertEquals(kvs[kvs.length - 1], results.get(0));
550    }
551    // Scan limited TimeRange
552    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
553    scanSpec.setTimeRange(1, 3);
554    scanSpec.readAllVersions();
555    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
556      results = new ArrayList<>();
557      assertEquals(true, scan.next(results));
558      assertEquals(2, results.size());
559    }
560    // Another range.
561    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
562    scanSpec.setTimeRange(5, 10);
563    scanSpec.readAllVersions();
564    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
565      results = new ArrayList<>();
566      assertEquals(true, scan.next(results));
567      assertEquals(1, results.size());
568    }
569    // See how TimeRange and Versions interact.
570    // Another range.
571    scanSpec = new Scan().withStartRow(Bytes.toBytes(r1));
572    scanSpec.setTimeRange(0, 10);
573    scanSpec.readVersions(3);
574    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
575      results = new ArrayList<>();
576      assertEquals(true, scan.next(results));
577      assertEquals(3, results.size());
578    }
579  }
580
581  @Test
582  public void testScanSameTimestamp() throws IOException {
583    // returns only 1 of these 2 even though same timestamp
584    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
585      create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"), };
586    List<KeyValueScanner> scanners = Arrays
587      .asList(new KeyValueScanner[] { new KeyValueScanFixture(CellComparator.getInstance(), kvs) });
588
589    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
590    // this only uses maxVersions (default=1) and TimeRange (default=all)
591    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, null, scanners)) {
592      List<Cell> results = new ArrayList<>();
593      assertEquals(true, scan.next(results));
594      assertEquals(1, results.size());
595      assertEquals(1, scan.memstoreOnlyReads);
596      assertEquals(kvs[0], results.get(0));
597    }
598  }
599
600  @Test
601  public void testNonUserScan() throws IOException {
602    // returns only 1 of these 2 even though same timestamp
603    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
604      create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"), };
605    List<KeyValueScanner> scanners = Arrays
606      .asList(new KeyValueScanner[] { new KeyValueScanFixture(CellComparator.getInstance(), kvs) });
607
608    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
609    // this only uses maxVersions (default=1) and TimeRange (default=all)
610    try (StoreScanner scan =
611      new StoreScanner(scanSpec, scanInfo, null, scanners, ScanType.COMPACT_RETAIN_DELETES)) {
612      List<Cell> results = new ArrayList<>();
613      assertEquals(true, scan.next(results));
614      assertEquals(1, results.size());
615      // the type is not a user scan. so it won't account for the memstore reads
616      assertEquals(0, scan.memstoreOnlyReads);
617      assertEquals(kvs[0], results.get(0));
618    }
619  }
620
621  /*
622   * Test test shows exactly how the matcher's return codes confuses the StoreScanner and prevent it
623   * from doing the right thing. Seeking once, then nexting twice should return R1, then R2, but in
624   * this case it doesnt. TODO this comment makes no sense above. Appears to do the right thing.
625   */
626  @Test
627  public void testWontNextToNext() throws IOException {
628    // build the scan file:
629    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
630      create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
631      create("R2", "cf", "a", 1, KeyValue.Type.Put, "dont-care") };
632    List<KeyValueScanner> scanners = scanFixture(kvs);
633
634    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
635    // this only uses maxVersions (default=1) and TimeRange (default=all)
636    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
637      List<Cell> results = new ArrayList<>();
638      scan.next(results);
639      assertEquals(1, results.size());
640      assertEquals(kvs[0], results.get(0));
641      // should be ok...
642      // now scan _next_ again.
643      results.clear();
644      scan.next(results);
645      assertEquals(1, results.size());
646      assertEquals(kvs[2], results.get(0));
647
648      results.clear();
649      scan.next(results);
650      assertEquals(0, results.size());
651    }
652  }
653
654  @Test
655  public void testDeleteVersionSameTimestamp() throws IOException {
656    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
657      create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"), };
658    List<KeyValueScanner> scanners = scanFixture(kvs);
659    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
660    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
661      List<Cell> results = new ArrayList<>();
662      assertFalse(scan.next(results));
663      assertEquals(0, results.size());
664    }
665  }
666
667  /*
668   * Test the case where there is a delete row 'in front of' the next row, the scanner will move to
669   * the next row.
670   */
671  @Test
672  public void testDeletedRowThenGoodRow() throws IOException {
673    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
674      create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"),
675      create("R2", "cf", "a", 20, KeyValue.Type.Put, "dont-care") };
676    List<KeyValueScanner> scanners = scanFixture(kvs);
677    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
678    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
679      List<Cell> results = new ArrayList<>();
680      assertEquals(true, scan.next(results));
681      assertEquals(0, results.size());
682
683      assertEquals(true, scan.next(results));
684      assertEquals(1, results.size());
685      assertEquals(kvs[2], results.get(0));
686
687      assertEquals(false, scan.next(results));
688    }
689  }
690
691  @Test
692  public void testDeleteVersionMaskingMultiplePuts() throws IOException {
693    long now = EnvironmentEdgeManager.currentTime();
694    KeyValue[] kvs1 = new KeyValue[] { create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
695      create("R1", "cf", "a", now, KeyValue.Type.Delete, "dont-care") };
696    KeyValue[] kvs2 =
697      new KeyValue[] { create("R1", "cf", "a", now - 500, KeyValue.Type.Put, "dont-care"),
698        create("R1", "cf", "a", now - 100, KeyValue.Type.Put, "dont-care"),
699        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care") };
700    List<KeyValueScanner> scanners = scanFixture(kvs1, kvs2);
701
702    try (StoreScanner scan = new StoreScanner(new Scan().withStartRow(Bytes.toBytes("R1")),
703      scanInfo, getCols("a"), scanners)) {
704      List<Cell> results = new ArrayList<>();
705      // the two put at ts=now will be masked by the 1 delete, and
706      // since the scan default returns 1 version we'll return the newest
707      // key, which is kvs[2], now-100.
708      assertEquals(true, scan.next(results));
709      assertEquals(1, results.size());
710      assertEquals(kvs2[1], results.get(0));
711    }
712  }
713
714  @Test
715  public void testDeleteVersionsMixedAndMultipleVersionReturn() throws IOException {
716    long now = EnvironmentEdgeManager.currentTime();
717    KeyValue[] kvs1 = new KeyValue[] { create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
718      create("R1", "cf", "a", now, KeyValue.Type.Delete, "dont-care") };
719    KeyValue[] kvs2 =
720      new KeyValue[] { create("R1", "cf", "a", now - 500, KeyValue.Type.Put, "dont-care"),
721        create("R1", "cf", "a", now + 500, KeyValue.Type.Put, "dont-care"),
722        create("R1", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
723        create("R2", "cf", "z", now, KeyValue.Type.Put, "dont-care") };
724    List<KeyValueScanner> scanners = scanFixture(kvs1, kvs2);
725
726    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1")).readVersions(2);
727    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
728      List<Cell> results = new ArrayList<>();
729      assertEquals(true, scan.next(results));
730      assertEquals(2, results.size());
731      assertEquals(kvs2[1], results.get(0));
732      assertEquals(kvs2[0], results.get(1));
733    }
734  }
735
736  @Test
737  public void testWildCardOneVersionScan() throws IOException {
738    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
739      create("R1", "cf", "b", 1, KeyValue.Type.Put, "dont-care"),
740      create("R1", "cf", "a", 1, KeyValue.Type.DeleteColumn, "dont-care"), };
741    List<KeyValueScanner> scanners = scanFixture(kvs);
742    try (StoreScanner scan =
743      new StoreScanner(new Scan().withStartRow(Bytes.toBytes("R1")), scanInfo, null, scanners)) {
744      List<Cell> results = new ArrayList<>();
745      assertEquals(true, scan.next(results));
746      assertEquals(2, results.size());
747      assertEquals(kvs[0], results.get(0));
748      assertEquals(kvs[1], results.get(1));
749    }
750  }
751
752  @Test
753  public void testWildCardScannerUnderDeletes() throws IOException {
754    KeyValue[] kvs = new KeyValue[] {
755      // inc
756      create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
757      // orphaned delete column.
758      create("R1", "cf", "a", 1, KeyValue.Type.DeleteColumn, "dont-care"),
759      // column b
760      // inc
761      create("R1", "cf", "b", 2, KeyValue.Type.Put, "dont-care"),
762      // inc
763      create("R1", "cf", "b", 1, KeyValue.Type.Put, "dont-care"),
764      // column c
765      create("R1", "cf", "c", 10, KeyValue.Type.Delete, "dont-care"),
766      // no
767      create("R1", "cf", "c", 10, KeyValue.Type.Put, "dont-care"),
768      // inc
769      create("R1", "cf", "c", 9, KeyValue.Type.Put, "dont-care"),
770      // column d
771      // inc
772      create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
773      create("R1", "cf", "d", 10, KeyValue.Type.DeleteColumn, "dont-care"),
774      // no
775      create("R1", "cf", "d", 9, KeyValue.Type.Put, "dont-care"),
776      // no
777      create("R1", "cf", "d", 8, KeyValue.Type.Put, "dont-care"),
778
779    };
780    List<KeyValueScanner> scanners = scanFixture(kvs);
781    try (
782      StoreScanner scan = new StoreScanner(new Scan().readVersions(2), scanInfo, null, scanners)) {
783      List<Cell> results = new ArrayList<>();
784      assertEquals(true, scan.next(results));
785      assertEquals(5, results.size());
786      assertEquals(kvs[0], results.get(0));
787      assertEquals(kvs[2], results.get(1));
788      assertEquals(kvs[3], results.get(2));
789      assertEquals(kvs[6], results.get(3));
790      assertEquals(kvs[7], results.get(4));
791    }
792  }
793
794  @Test
795  public void testDeleteFamily() throws IOException {
796    KeyValue[] kvs =
797      new KeyValue[] { create("R1", "cf", "a", 100, KeyValue.Type.DeleteFamily, "dont-care"),
798        create("R1", "cf", "b", 11, KeyValue.Type.Put, "dont-care"),
799        create("R1", "cf", "c", 11, KeyValue.Type.Put, "dont-care"),
800        create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
801        create("R1", "cf", "e", 11, KeyValue.Type.Put, "dont-care"),
802        create("R1", "cf", "e", 11, KeyValue.Type.DeleteColumn, "dont-care"),
803        create("R1", "cf", "f", 11, KeyValue.Type.Put, "dont-care"),
804        create("R1", "cf", "g", 11, KeyValue.Type.Put, "dont-care"),
805        create("R1", "cf", "g", 11, KeyValue.Type.Delete, "dont-care"),
806        create("R1", "cf", "h", 11, KeyValue.Type.Put, "dont-care"),
807        create("R1", "cf", "i", 11, KeyValue.Type.Put, "dont-care"),
808        create("R2", "cf", "a", 11, KeyValue.Type.Put, "dont-care"), };
809    List<KeyValueScanner> scanners = scanFixture(kvs);
810    try (StoreScanner scan =
811      new StoreScanner(new Scan().readAllVersions(), scanInfo, null, scanners)) {
812      List<Cell> results = new ArrayList<>();
813      assertEquals(true, scan.next(results));
814      assertEquals(0, results.size());
815      assertEquals(true, scan.next(results));
816      assertEquals(1, results.size());
817      assertEquals(kvs[kvs.length - 1], results.get(0));
818
819      assertEquals(false, scan.next(results));
820    }
821  }
822
823  @Test
824  public void testDeleteColumn() throws IOException {
825    KeyValue[] kvs =
826      new KeyValue[] { create("R1", "cf", "a", 10, KeyValue.Type.DeleteColumn, "dont-care"),
827        create("R1", "cf", "a", 9, KeyValue.Type.Delete, "dont-care"),
828        create("R1", "cf", "a", 8, KeyValue.Type.Put, "dont-care"),
829        create("R1", "cf", "b", 5, KeyValue.Type.Put, "dont-care") };
830    List<KeyValueScanner> scanners = scanFixture(kvs);
831    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, null, scanners)) {
832      List<Cell> results = new ArrayList<>();
833      assertEquals(true, scan.next(results));
834      assertEquals(1, results.size());
835      assertEquals(kvs[3], results.get(0));
836    }
837  }
838
839  private static final KeyValue[] kvs =
840    new KeyValue[] { create("R1", "cf", "a", 11, KeyValue.Type.Put, "dont-care"),
841      create("R1", "cf", "b", 11, KeyValue.Type.Put, "dont-care"),
842      create("R1", "cf", "c", 11, KeyValue.Type.Put, "dont-care"),
843      create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
844      create("R1", "cf", "e", 11, KeyValue.Type.Put, "dont-care"),
845      create("R1", "cf", "f", 11, KeyValue.Type.Put, "dont-care"),
846      create("R1", "cf", "g", 11, KeyValue.Type.Put, "dont-care"),
847      create("R1", "cf", "h", 11, KeyValue.Type.Put, "dont-care"),
848      create("R1", "cf", "i", 11, KeyValue.Type.Put, "dont-care"),
849      create("R2", "cf", "a", 11, KeyValue.Type.Put, "dont-care"), };
850
851  @Test
852  public void testSkipColumn() throws IOException {
853    List<KeyValueScanner> scanners = scanFixture(kvs);
854    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, getCols("a", "d"), scanners)) {
855      List<Cell> results = new ArrayList<>();
856      assertEquals(true, scan.next(results));
857      assertEquals(2, results.size());
858      assertEquals(kvs[0], results.get(0));
859      assertEquals(kvs[3], results.get(1));
860      results.clear();
861
862      assertEquals(true, scan.next(results));
863      assertEquals(1, results.size());
864      assertEquals(kvs[kvs.length - 1], results.get(0));
865
866      results.clear();
867      assertEquals(false, scan.next(results));
868    }
869  }
870
871  /*
872   * Test expiration of KeyValues in combination with a configured TTL for a column family (as
873   * should be triggered in a major compaction).
874   */
875  @Test
876  public void testWildCardTtlScan() throws IOException {
877    long now = EnvironmentEdgeManager.currentTime();
878    KeyValue[] kvs =
879      new KeyValue[] { create("R1", "cf", "a", now - 1000, KeyValue.Type.Put, "dont-care"),
880        create("R1", "cf", "b", now - 10, KeyValue.Type.Put, "dont-care"),
881        create("R1", "cf", "c", now - 200, KeyValue.Type.Put, "dont-care"),
882        create("R1", "cf", "d", now - 10000, KeyValue.Type.Put, "dont-care"),
883        create("R2", "cf", "a", now, KeyValue.Type.Put, "dont-care"),
884        create("R2", "cf", "b", now - 10, KeyValue.Type.Put, "dont-care"),
885        create("R2", "cf", "c", now - 200, KeyValue.Type.Put, "dont-care"),
886        create("R2", "cf", "c", now - 1000, KeyValue.Type.Put, "dont-care") };
887    List<KeyValueScanner> scanners = scanFixture(kvs);
888    Scan scan = new Scan();
889    scan.readVersions(1);
890    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
891      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
892    try (StoreScanner scanner = new StoreScanner(scan, scanInfo, null, scanners)) {
893      List<Cell> results = new ArrayList<>();
894      assertEquals(true, scanner.next(results));
895      assertEquals(2, results.size());
896      assertEquals(kvs[1], results.get(0));
897      assertEquals(kvs[2], results.get(1));
898      results.clear();
899
900      assertEquals(true, scanner.next(results));
901      assertEquals(3, results.size());
902      assertEquals(kvs[4], results.get(0));
903      assertEquals(kvs[5], results.get(1));
904      assertEquals(kvs[6], results.get(2));
905      results.clear();
906
907      assertEquals(false, scanner.next(results));
908    }
909  }
910
911  @Test
912  public void testScannerReseekDoesntNPE() throws Exception {
913    List<KeyValueScanner> scanners = scanFixture(kvs);
914    try (StoreScanner scan = new StoreScanner(new Scan(), scanInfo, getCols("a", "d"), scanners)) {
915      // Previously a updateReaders twice in a row would cause an NPE. In test this would also
916      // normally cause an NPE because scan.store is null. So as long as we get through these
917      // two calls we are good and the bug was quashed.
918      scan.updateReaders(Collections.emptyList(), Collections.emptyList());
919      scan.updateReaders(Collections.emptyList(), Collections.emptyList());
920      scan.peek();
921    }
922  }
923
924  @Test
925  @Ignore("this fails, since we don't handle deletions, etc, in peek")
926  public void testPeek() throws Exception {
927    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care"),
928      create("R1", "cf", "a", 1, KeyValue.Type.Delete, "dont-care"), };
929    List<KeyValueScanner> scanners = scanFixture(kvs);
930    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1"));
931    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, getCols("a"), scanners)) {
932      assertNull(scan.peek());
933    }
934  }
935
936  /**
937   * Ensure that expired delete family markers don't override valid puts
938   */
939  @Test
940  public void testExpiredDeleteFamily() throws Exception {
941    long now = EnvironmentEdgeManager.currentTime();
942    KeyValue[] kvs = new KeyValue[] {
943      new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null, now - 1000,
944        KeyValue.Type.DeleteFamily),
945      create("R1", "cf", "a", now - 10, KeyValue.Type.Put, "dont-care"), };
946    List<KeyValueScanner> scanners = scanFixture(kvs);
947    Scan scan = new Scan();
948    scan.readVersions(1);
949    // scanner with ttl equal to 500
950    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
951      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
952    try (StoreScanner scanner = new StoreScanner(scan, scanInfo, null, scanners)) {
953      List<Cell> results = new ArrayList<>();
954      assertEquals(true, scanner.next(results));
955      assertEquals(1, results.size());
956      assertEquals(kvs[1], results.get(0));
957      results.clear();
958
959      assertEquals(false, scanner.next(results));
960    }
961  }
962
963  @Test
964  public void testDeleteMarkerLongevity() throws Exception {
965    try {
966      final long now = EnvironmentEdgeManager.currentTime();
967      EnvironmentEdgeManagerTestHelper.injectEdge(new EnvironmentEdge() {
968        @Override
969        public long currentTime() {
970          return now;
971        }
972      });
973      // @formatter:off
974      KeyValue[] kvs = new KeyValue[]{
975        /*0*/ new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null,
976        now - 100, KeyValue.Type.DeleteFamily), // live
977        /*1*/ new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null,
978        now - 1000, KeyValue.Type.DeleteFamily), // expired
979        /*2*/ create("R1", "cf", "a", now - 50,
980        KeyValue.Type.Put, "v3"), // live
981        /*3*/ create("R1", "cf", "a", now - 55,
982        KeyValue.Type.Delete, "dontcare"), // live
983        /*4*/ create("R1", "cf", "a", now - 55,
984        KeyValue.Type.Put, "deleted-version v2"), // deleted
985        /*5*/ create("R1", "cf", "a", now - 60,
986        KeyValue.Type.Put, "v1"), // live
987        /*6*/ create("R1", "cf", "a", now - 65,
988        KeyValue.Type.Put, "v0"), // max-version reached
989        /*7*/ create("R1", "cf", "a",
990        now - 100, KeyValue.Type.DeleteColumn, "dont-care"), // max-version
991        /*8*/ create("R1", "cf", "b", now - 600,
992        KeyValue.Type.DeleteColumn, "dont-care"), // expired
993        /*9*/ create("R1", "cf", "b", now - 70,
994        KeyValue.Type.Put, "v2"), // live
995        /*10*/ create("R1", "cf", "b", now - 750,
996        KeyValue.Type.Put, "v1"), // expired
997        /*11*/ create("R1", "cf", "c", now - 500,
998        KeyValue.Type.Delete, "dontcare"), // expired
999        /*12*/ create("R1", "cf", "c", now - 600,
1000        KeyValue.Type.Put, "v1"), // expired
1001        /*13*/ create("R1", "cf", "c", now - 1000,
1002        KeyValue.Type.Delete, "dontcare"), // expired
1003        /*14*/ create("R1", "cf", "d", now - 60,
1004        KeyValue.Type.Put, "expired put"), // live
1005        /*15*/ create("R1", "cf", "d", now - 100,
1006        KeyValue.Type.Delete, "not-expired delete"), // live
1007      };
1008      // @formatter:on
1009      List<KeyValueScanner> scanners = scanFixture(kvs);
1010      ScanInfo scanInfo = new ScanInfo(CONF, Bytes.toBytes("cf"), 0 /* minVersions */,
1011        2 /* maxVersions */, 500 /* ttl */, KeepDeletedCells.FALSE /* keepDeletedCells */,
1012        HConstants.DEFAULT_BLOCKSIZE /* block size */, 200, /* timeToPurgeDeletes */
1013        CellComparator.getInstance(), false);
1014      try (StoreScanner scanner =
1015        new StoreScanner(scanInfo, 2, ScanType.COMPACT_DROP_DELETES, scanners)) {
1016        List<Cell> results = new ArrayList<>();
1017        results = new ArrayList<>();
1018        assertEquals(true, scanner.next(results));
1019        assertEquals(kvs[0], results.get(0));
1020        assertEquals(kvs[2], results.get(1));
1021        assertEquals(kvs[3], results.get(2));
1022        assertEquals(kvs[5], results.get(3));
1023        assertEquals(kvs[9], results.get(4));
1024        assertEquals(kvs[14], results.get(5));
1025        assertEquals(kvs[15], results.get(6));
1026        assertEquals(7, results.size());
1027      }
1028    } finally {
1029      EnvironmentEdgeManagerTestHelper.reset();
1030    }
1031  }
1032
1033  @Test
1034  public void testPreadNotEnabledForCompactionStoreScanners() throws Exception {
1035    long now = EnvironmentEdgeManager.currentTime();
1036    KeyValue[] kvs = new KeyValue[] {
1037      new KeyValue(Bytes.toBytes("R1"), Bytes.toBytes("cf"), null, now - 1000,
1038        KeyValue.Type.DeleteFamily),
1039      create("R1", "cf", "a", now - 10, KeyValue.Type.Put, "dont-care"), };
1040    List<KeyValueScanner> scanners = scanFixture(kvs);
1041    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, 500, KeepDeletedCells.FALSE,
1042      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
1043    try (StoreScanner storeScanner =
1044      new StoreScanner(scanInfo, -1, ScanType.COMPACT_RETAIN_DELETES, scanners)) {
1045      assertFalse(storeScanner.isScanUsePread());
1046    }
1047  }
1048
1049  @Test
1050  public void testReadVersionWithRawAndFilter() throws IOException {
1051    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, Long.MAX_VALUE, KeepDeletedCells.FALSE,
1052      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
1053    KeyValue[] kvs = new KeyValue[] { create("R1", "cf", "a", 3, KeyValue.Type.Put, "dont-care"),
1054      create("R1", "cf", "a", 2, KeyValue.Type.Put, "dont-care"),
1055      create("R1", "cf", "a", 1, KeyValue.Type.Put, "dont-care") };
1056    List<KeyValueScanner> scanners = Arrays
1057      .asList(new KeyValueScanner[] { new KeyValueScanFixture(CellComparator.getInstance(), kvs) });
1058
1059    BinaryComparator comp = new BinaryComparator(Bytes.toBytes("a"));
1060    Filter filter = new QualifierFilter(CompareOperator.EQUAL, comp);
1061    Scan scanSpec = new Scan().withStartRow(Bytes.toBytes("R1")).readVersions(2).setRaw(true);
1062    scanSpec.setFilter(filter);
1063    try (StoreScanner scan = new StoreScanner(scanSpec, scanInfo, null, scanners)) {
1064      List<Cell> results = new ArrayList<>();
1065      assertEquals(true, scan.next(results));
1066      assertEquals(2, results.size());
1067    }
1068  }
1069
1070  @Test
1071  public void testScannersClosedWhenCheckingOnlyMemStore() throws IOException {
1072    class MyCollectionBackedScanner extends CollectionBackedScanner {
1073      final boolean fileScanner;
1074      boolean closed;
1075
1076      MyCollectionBackedScanner(boolean fileScanner) {
1077        super(Collections.emptySortedSet());
1078        this.fileScanner = fileScanner;
1079      }
1080
1081      @Override
1082      public boolean isFileScanner() {
1083        return fileScanner;
1084      }
1085
1086      @Override
1087      public void close() {
1088        super.close();
1089        closed = true;
1090      }
1091    }
1092
1093    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, 1, Long.MAX_VALUE, KeepDeletedCells.FALSE,
1094      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
1095    InternalScan scan = new InternalScan(new Scan());
1096    scan.checkOnlyMemStore();
1097    MyCollectionBackedScanner fileScanner = new MyCollectionBackedScanner(true);
1098    MyCollectionBackedScanner memStoreScanner = new MyCollectionBackedScanner(false);
1099    List<? extends KeyValueScanner> allScanners = Arrays.asList(fileScanner, memStoreScanner);
1100
1101    try (StoreScanner scanner = new StoreScanner(scan, scanInfo, null, allScanners)) {
1102      List<KeyValueScanner> remaining = scanner.selectScannersFrom(null, allScanners);
1103
1104      assertEquals(1, remaining.size());
1105      assertSame(memStoreScanner, remaining.get(0));
1106      assertTrue(fileScanner.closed);
1107      assertFalse(memStoreScanner.closed);
1108    }
1109  }
1110
1111  @Test
1112  public void testGetFilesRead() throws Exception {
1113    // Setup: test util, conf, fs, cache, region fs, and HFile context.
1114    HBaseTestingUtil testUtil = new HBaseTestingUtil();
1115    Configuration conf = testUtil.getConfiguration();
1116    Path testDir = testUtil.getDataTestDir(name.getMethodName() + "_directory");
1117    FileSystem fs = testDir.getFileSystem(conf);
1118    CacheConfig cacheConf = new CacheConfig(conf);
1119    final String TEST_FAMILY = "cf";
1120
1121    final RegionInfo hri =
1122      RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build();
1123    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs,
1124      new Path(testDir, hri.getTable().getNameAsString()), hri);
1125    HFileContext hFileContext = new HFileContextBuilder().withBlockSize(8 * 1024).build();
1126
1127    StoreFileTracker sft = StoreFileTrackerFactory.create(conf, false,
1128      StoreContext.getBuilder()
1129        .withFamilyStoreDirectoryPath(new Path(regionFs.getRegionDir(), TEST_FAMILY))
1130        .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY))
1131        .withRegionFileSystem(regionFs).build());
1132
1133    long now = EnvironmentEdgeManager.currentTime();
1134    List<Path> filePaths = new ArrayList<>();
1135    List<HStoreFile> storeFiles = new ArrayList<>();
1136
1137    // File 1: rows "row01" to "row05" - in scan key range, fresh timestamp
1138    StoreFileWriter writer1 = new StoreFileWriter.Builder(conf, cacheConf, fs)
1139      .withFilePath(regionFs.createTempName()).withFileContext(hFileContext).build();
1140    for (int i = 1; i <= 5; i++) {
1141      writer1.append(new KeyValue(Bytes.toBytes(String.format("row%02d", i)), CF,
1142        Bytes.toBytes("col"), now, Bytes.toBytes("value" + i)));
1143    }
1144    Path path1 = regionFs.commitStoreFile(TEST_FAMILY, writer1.getPath());
1145    writer1.close();
1146    filePaths.add(fs.makeQualified(path1));
1147    HStoreFile file1 = new HStoreFile(fs, path1, conf, cacheConf, BloomType.NONE, true, sft);
1148    file1.initReader();
1149    storeFiles.add(file1);
1150
1151    // File 2: rows "row06" to "row10" - in scan key range, fresh timestamp
1152    StoreFileWriter writer2 = new StoreFileWriter.Builder(conf, cacheConf, fs)
1153      .withFilePath(regionFs.createTempName()).withFileContext(hFileContext).build();
1154    for (int i = 6; i <= 10; i++) {
1155      writer2.append(new KeyValue(Bytes.toBytes(String.format("row%02d", i)), CF,
1156        Bytes.toBytes("col"), now, Bytes.toBytes("value" + i)));
1157    }
1158    Path path2 = regionFs.commitStoreFile(TEST_FAMILY, writer2.getPath());
1159    writer2.close();
1160    filePaths.add(fs.makeQualified(path2));
1161    HStoreFile file2 = new HStoreFile(fs, path2, conf, cacheConf, BloomType.NONE, true, sft);
1162    file2.initReader();
1163    storeFiles.add(file2);
1164
1165    // File 3: rows "row20" to "row25" - OUT of scan key range (after stop row)
1166    StoreFileWriter writer3 = new StoreFileWriter.Builder(conf, cacheConf, fs)
1167      .withFilePath(regionFs.createTempName()).withFileContext(hFileContext).build();
1168    for (int i = 20; i <= 25; i++) {
1169      writer3.append(new KeyValue(Bytes.toBytes(String.format("row%02d", i)), CF,
1170        Bytes.toBytes("col"), now, Bytes.toBytes("value" + i)));
1171    }
1172    Path path3 = regionFs.commitStoreFile(TEST_FAMILY, writer3.getPath());
1173    writer3.close();
1174    filePaths.add(fs.makeQualified(path3));
1175    HStoreFile file3 = new HStoreFile(fs, path3, conf, cacheConf, BloomType.NONE, true, sft);
1176    file3.initReader();
1177    storeFiles.add(file3);
1178
1179    // File 4: row "row00" - OUT of key range (before start row)
1180    StoreFileWriter writer4 = new StoreFileWriter.Builder(conf, cacheConf, fs)
1181      .withFilePath(regionFs.createTempName()).withFileContext(hFileContext).build();
1182    writer4.append(
1183      new KeyValue(Bytes.toBytes("row00"), CF, Bytes.toBytes("col"), now, Bytes.toBytes("value0")));
1184    Path path4 = regionFs.commitStoreFile(TEST_FAMILY, writer4.getPath());
1185    writer4.close();
1186    filePaths.add(fs.makeQualified(path4));
1187    HStoreFile file4 = new HStoreFile(fs, path4, conf, cacheConf, BloomType.NONE, true, sft);
1188    file4.initReader();
1189    storeFiles.add(file4);
1190
1191    // File 5: row "row11" with expired timestamp (1 hour ago); TTL-filtered but still tracked.
1192    long expiredTime = now - (1000 * 60 * 60);
1193    StoreFileWriter writer5 = new StoreFileWriter.Builder(conf, cacheConf, fs)
1194      .withFilePath(regionFs.createTempName()).withFileContext(hFileContext).build();
1195    writer5.append(new KeyValue(Bytes.toBytes("row11"), CF, Bytes.toBytes("col"), expiredTime,
1196      Bytes.toBytes("expired_value")));
1197    Path path5 = regionFs.commitStoreFile(TEST_FAMILY, writer5.getPath());
1198    writer5.close();
1199    filePaths.add(fs.makeQualified(path5));
1200    HStoreFile file5 = new HStoreFile(fs, path5, conf, cacheConf, BloomType.NONE, true, sft);
1201    file5.initReader();
1202    storeFiles.add(file5);
1203
1204    // Create StoreFileScanners from all five files.
1205    List<KeyValueScanner> scanners = new ArrayList<>();
1206    for (HStoreFile storeFile : storeFiles) {
1207      StoreFileReader reader = storeFile.getReader();
1208      StoreFileScanner scanner = reader.getStoreFileScanner(false, false, false, 0, 0, false);
1209      scanners.add(scanner);
1210    }
1211
1212    // Scan row01–row15 with 30-minute TTL so file 5's expired cell is filtered after read.
1213    Scan scan =
1214      new Scan().withStartRow(Bytes.toBytes("row01")).withStopRow(Bytes.toBytes("row15"), false);
1215    long ttl = 30 * 60 * 1000;
1216    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, Integer.MAX_VALUE, ttl, KeepDeletedCells.FALSE,
1217      HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
1218
1219    // Create StoreScanner; drain with next(), then close.
1220    StoreScanner storeScanner = new StoreScanner(scan, scanInfo, null, scanners);
1221
1222    List<Cell> results = new ArrayList<>();
1223    while (storeScanner.next(results)) {
1224      results.clear();
1225    }
1226    storeScanner.close();
1227
1228    // After close: all 5 files must be tracked (in-range, out-of-range, and TTL-expired).
1229    Set<Path> filesRead = storeScanner.getFilesRead();
1230
1231    assertTrue("File 1 (in range) should be tracked", filesRead.contains(filePaths.get(0)));
1232    assertTrue("File 2 (in range) should be tracked", filesRead.contains(filePaths.get(1)));
1233    assertTrue("File 3 (out of key range) should be tracked", filesRead.contains(filePaths.get(2)));
1234    assertTrue("File 4 (before start row) should be tracked", filesRead.contains(filePaths.get(3)));
1235    assertTrue("File 5 (expired TTL, filtered after read) should be tracked",
1236      filesRead.contains(filePaths.get(4)));
1237    assertEquals("Should have all 5 files read", 5, filesRead.size());
1238  }
1239
1240  /**
1241   * Test that when StoreScanner initialization fails after scanners are created, files are not
1242   * tracked
1243   */
1244  @Test
1245  public void testGetFilesReadOnInitializationFailure() throws Exception {
1246    HStore mockStore = Mockito.mock(HStore.class);
1247    ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, Integer.MAX_VALUE, Long.MAX_VALUE,
1248      KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, CellComparator.getInstance(), false);
1249    Scan scan = new Scan();
1250    NavigableSet<byte[]> columns = null;
1251    long readPt = 100L;
1252
1253    // Create mock scanners that will be returned by getScanners
1254    KeyValueScanner mockScanner1 = Mockito.mock(StoreFileScanner.class);
1255    KeyValueScanner mockScanner2 = Mockito.mock(StoreFileScanner.class);
1256    Path filePath1 = new Path("/test/file1");
1257    Path filePath2 = new Path("/test/file2");
1258    Mockito.when(mockScanner1.isFileScanner()).thenReturn(true);
1259    Mockito.when(mockScanner2.isFileScanner()).thenReturn(true);
1260    Mockito.doReturn(true).when(mockScanner1).shouldUseScanner(Mockito.any(), Mockito.any(),
1261      Mockito.anyLong());
1262    Mockito.doReturn(true).when(mockScanner2).shouldUseScanner(Mockito.any(), Mockito.any(),
1263      Mockito.anyLong());
1264    Mockito.when(mockScanner1.getFilesRead()).thenReturn(Collections.singleton(filePath1));
1265    Mockito.when(mockScanner2.getFilesRead()).thenReturn(Collections.singleton(filePath2));
1266
1267    List<KeyValueScanner> mockScanners = new ArrayList<>();
1268    mockScanners.add(mockScanner1);
1269    mockScanners.add(mockScanner2);
1270
1271    // Make getScanners return the mock scanners
1272    Mockito.when(mockStore.getScanners(Mockito.anyBoolean(), Mockito.anyBoolean(),
1273      Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(),
1274      Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(mockScanners);
1275
1276    Mockito.when(mockStore.getCoprocessorHost()).thenReturn(null);
1277
1278    // Make seek throw IOException on one scanner to simulate failure during seekScanners
1279    Mockito.doThrow(new IOException("Test seek failure")).when(mockScanner1).seek(Mockito.any());
1280
1281    // Verify that IOException is thrown during construction
1282    StoreScanner storeScanner = null;
1283    IOException caughtException = null;
1284    try {
1285      storeScanner = new StoreScanner(mockStore, scanInfo, scan, columns, readPt);
1286    } catch (IOException e) {
1287      caughtException = e;
1288    }
1289
1290    // Verify that exception was thrown
1291    assertNotNull("Should have thrown IOException during initialization", caughtException);
1292
1293    // Verify that store methods were called (cleanup happened in catch block)
1294    Mockito.verify(mockStore, Mockito.times(1)).addChangedReaderObserver(Mockito.any());
1295    Mockito.verify(mockStore, Mockito.times(1)).deleteChangedReaderObserver(Mockito.any());
1296
1297    // Verify that scanners were closed (clearAndClose was called in catch block)
1298    Mockito.verify(mockScanner1, Mockito.times(1)).close();
1299    Mockito.verify(mockScanner2, Mockito.times(1)).close();
1300
1301    // Verify that getFilesRead was NOT called on the scanners
1302    // (because trackFiles=false was passed to clearAndClose, so files weren't tracked)
1303    Mockito.verify(mockScanner1, Mockito.never()).getFilesRead();
1304    Mockito.verify(mockScanner2, Mockito.never()).getFilesRead();
1305  }
1306
1307}