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}