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}