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.regionserver.HStoreFile.BLOOM_FILTER_TYPE_KEY; 021import static org.apache.hadoop.hbase.regionserver.HStoreFile.DELETE_FAMILY_COUNT; 022import static org.apache.hadoop.hbase.regionserver.HStoreFile.LAST_BLOOM_KEY; 023 024import java.io.DataInput; 025import java.io.IOException; 026import java.util.Map; 027import java.util.Optional; 028import java.util.SortedSet; 029import java.util.concurrent.atomic.AtomicInteger; 030 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.Cell; 035import org.apache.hadoop.hbase.CellComparator; 036import org.apache.hadoop.hbase.HBaseInterfaceAudience; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.PrivateCellUtil; 039import org.apache.hadoop.hbase.KeyValue; 040import org.apache.hadoop.hbase.client.Scan; 041import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper; 042import org.apache.hadoop.hbase.io.TimeRange; 043import org.apache.hadoop.hbase.io.hfile.BlockType; 044import org.apache.hadoop.hbase.io.hfile.CacheConfig; 045import org.apache.hadoop.hbase.io.hfile.HFile; 046import org.apache.hadoop.hbase.io.hfile.HFileBlock; 047import org.apache.hadoop.hbase.io.hfile.HFileScanner; 048import org.apache.hadoop.hbase.nio.ByteBuff; 049import org.apache.hadoop.hbase.util.BloomFilter; 050import org.apache.hadoop.hbase.util.BloomFilterFactory; 051import org.apache.hadoop.hbase.util.Bytes; 052import org.apache.yetus.audience.InterfaceAudience; 053import org.apache.yetus.audience.InterfaceStability; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 057 058/** 059 * Reader for a StoreFile. 060 */ 061@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.PHOENIX) 062@InterfaceStability.Evolving 063public class StoreFileReader { 064 private static final Logger LOG = LoggerFactory.getLogger(StoreFileReader.class.getName()); 065 066 protected BloomFilter generalBloomFilter = null; 067 protected BloomFilter deleteFamilyBloomFilter = null; 068 protected BloomType bloomFilterType; 069 private final HFile.Reader reader; 070 protected long sequenceID = -1; 071 protected TimeRange timeRange = null; 072 private byte[] lastBloomKey; 073 private long deleteFamilyCnt = -1; 074 private boolean bulkLoadResult = false; 075 private KeyValue.KeyOnlyKeyValue lastBloomKeyOnlyKV = null; 076 private boolean skipResetSeqId = true; 077 078 // Counter that is incremented every time a scanner is created on the 079 // store file. It is decremented when the scan on the store file is 080 // done. All StoreFileReader for the same StoreFile will share this counter. 081 private final AtomicInteger refCount; 082 083 // indicate that whether this StoreFileReader is shared, i.e., used for pread. If not, we will 084 // close the internal reader when readCompleted is called. 085 @VisibleForTesting 086 final boolean shared; 087 088 private StoreFileReader(HFile.Reader reader, AtomicInteger refCount, boolean shared) { 089 this.reader = reader; 090 bloomFilterType = BloomType.NONE; 091 this.refCount = refCount; 092 this.shared = shared; 093 } 094 095 public StoreFileReader(FileSystem fs, Path path, CacheConfig cacheConf, 096 boolean primaryReplicaStoreFile, AtomicInteger refCount, boolean shared, Configuration conf) 097 throws IOException { 098 this(HFile.createReader(fs, path, cacheConf, primaryReplicaStoreFile, conf), refCount, shared); 099 } 100 101 public StoreFileReader(FileSystem fs, Path path, FSDataInputStreamWrapper in, long size, 102 CacheConfig cacheConf, boolean primaryReplicaStoreFile, AtomicInteger refCount, 103 boolean shared, Configuration conf) throws IOException { 104 this(HFile.createReader(fs, path, in, size, cacheConf, primaryReplicaStoreFile, conf), refCount, 105 shared); 106 } 107 108 void copyFields(StoreFileReader reader) { 109 this.generalBloomFilter = reader.generalBloomFilter; 110 this.deleteFamilyBloomFilter = reader.deleteFamilyBloomFilter; 111 this.bloomFilterType = reader.bloomFilterType; 112 this.sequenceID = reader.sequenceID; 113 this.timeRange = reader.timeRange; 114 this.lastBloomKey = reader.lastBloomKey; 115 this.bulkLoadResult = reader.bulkLoadResult; 116 this.lastBloomKeyOnlyKV = reader.lastBloomKeyOnlyKV; 117 this.skipResetSeqId = reader.skipResetSeqId; 118 } 119 120 public boolean isPrimaryReplicaReader() { 121 return reader.isPrimaryReplicaReader(); 122 } 123 124 /** 125 * ONLY USE DEFAULT CONSTRUCTOR FOR UNIT TESTS 126 */ 127 @VisibleForTesting 128 StoreFileReader() { 129 this.refCount = new AtomicInteger(0); 130 this.reader = null; 131 this.shared = false; 132 } 133 134 public CellComparator getComparator() { 135 return reader.getComparator(); 136 } 137 138 /** 139 * Get a scanner to scan over this StoreFile. 140 * @param cacheBlocks should this scanner cache blocks? 141 * @param pread use pread (for highly concurrent small readers) 142 * @param isCompaction is scanner being used for compaction? 143 * @param scannerOrder Order of this scanner relative to other scanners. See 144 * {@link KeyValueScanner#getScannerOrder()}. 145 * @param canOptimizeForNonNullColumn {@code true} if we can make sure there is no null column, 146 * otherwise {@code false}. This is a hint for optimization. 147 * @return a scanner 148 */ 149 public StoreFileScanner getStoreFileScanner(boolean cacheBlocks, boolean pread, 150 boolean isCompaction, long readPt, long scannerOrder, boolean canOptimizeForNonNullColumn) { 151 return new StoreFileScanner(this, getScanner(cacheBlocks, pread, isCompaction), 152 !isCompaction, reader.hasMVCCInfo(), readPt, scannerOrder, canOptimizeForNonNullColumn); 153 } 154 155 /** 156 * Indicate that the scanner has started reading with this reader. We need to increment the ref 157 * count so reader is not close until some object is holding the lock 158 */ 159 void incrementRefCount() { 160 refCount.incrementAndGet(); 161 } 162 163 /** 164 * Indicate that the scanner has finished reading with this reader. We need to decrement the ref 165 * count, and also, if this is not the common pread reader, we should close it. 166 */ 167 void readCompleted() { 168 refCount.decrementAndGet(); 169 if (!shared) { 170 try { 171 reader.close(false); 172 } catch (IOException e) { 173 LOG.warn("failed to close stream reader", e); 174 } 175 } 176 } 177 178 /** 179 * @deprecated Do not write further code which depends on this call. Instead 180 * use getStoreFileScanner() which uses the StoreFileScanner class/interface 181 * which is the preferred way to scan a store with higher level concepts. 182 * 183 * @param cacheBlocks should we cache the blocks? 184 * @param pread use pread (for concurrent small readers) 185 * @return the underlying HFileScanner 186 */ 187 @Deprecated 188 public HFileScanner getScanner(boolean cacheBlocks, boolean pread) { 189 return getScanner(cacheBlocks, pread, false); 190 } 191 192 /** 193 * @deprecated Do not write further code which depends on this call. Instead 194 * use getStoreFileScanner() which uses the StoreFileScanner class/interface 195 * which is the preferred way to scan a store with higher level concepts. 196 * 197 * @param cacheBlocks 198 * should we cache the blocks? 199 * @param pread 200 * use pread (for concurrent small readers) 201 * @param isCompaction 202 * is scanner being used for compaction? 203 * @return the underlying HFileScanner 204 */ 205 @Deprecated 206 public HFileScanner getScanner(boolean cacheBlocks, boolean pread, 207 boolean isCompaction) { 208 return reader.getScanner(cacheBlocks, pread, isCompaction); 209 } 210 211 public void close(boolean evictOnClose) throws IOException { 212 reader.close(evictOnClose); 213 } 214 215 /** 216 * Check if this storeFile may contain keys within the TimeRange that 217 * have not expired (i.e. not older than oldestUnexpiredTS). 218 * @param timeRange the timeRange to restrict 219 * @param oldestUnexpiredTS the oldest timestamp that is not expired, as 220 * determined by the column family's TTL 221 * @return false if queried keys definitely don't exist in this StoreFile 222 */ 223 boolean passesTimerangeFilter(TimeRange tr, long oldestUnexpiredTS) { 224 return this.timeRange == null? true: 225 this.timeRange.includesTimeRange(tr) && this.timeRange.getMax() >= oldestUnexpiredTS; 226 } 227 228 /** 229 * Checks whether the given scan passes the Bloom filter (if present). Only 230 * checks Bloom filters for single-row or single-row-column scans. Bloom 231 * filter checking for multi-gets is implemented as part of the store 232 * scanner system (see {@link StoreFileScanner#seek(Cell)} and uses 233 * the lower-level API {@link #passesGeneralRowBloomFilter(byte[], int, int)} 234 * and {@link #passesGeneralRowColBloomFilter(Cell)}. 235 * 236 * @param scan the scan specification. Used to determine the row, and to 237 * check whether this is a single-row ("get") scan. 238 * @param columns the set of columns. Only used for row-column Bloom 239 * filters. 240 * @return true if the scan with the given column set passes the Bloom 241 * filter, or if the Bloom filter is not applicable for the scan. 242 * False if the Bloom filter is applicable and the scan fails it. 243 */ 244 boolean passesBloomFilter(Scan scan, final SortedSet<byte[]> columns) { 245 // Multi-column non-get scans will use Bloom filters through the 246 // lower-level API function that this function calls. 247 if (!scan.isGetScan()) { 248 return true; 249 } 250 251 byte[] row = scan.getStartRow(); 252 switch (this.bloomFilterType) { 253 case ROW: 254 return passesGeneralRowBloomFilter(row, 0, row.length); 255 256 case ROWCOL: 257 if (columns != null && columns.size() == 1) { 258 byte[] column = columns.first(); 259 // create the required fake key 260 Cell kvKey = PrivateCellUtil.createFirstOnRow(row, HConstants.EMPTY_BYTE_ARRAY, column); 261 return passesGeneralRowColBloomFilter(kvKey); 262 } 263 264 // For multi-column queries the Bloom filter is checked from the 265 // seekExact operation. 266 return true; 267 268 default: 269 return true; 270 } 271 } 272 273 public boolean passesDeleteFamilyBloomFilter(byte[] row, int rowOffset, 274 int rowLen) { 275 // Cache Bloom filter as a local variable in case it is set to null by 276 // another thread on an IO error. 277 BloomFilter bloomFilter = this.deleteFamilyBloomFilter; 278 279 // Empty file or there is no delete family at all 280 if (reader.getTrailer().getEntryCount() == 0 || deleteFamilyCnt == 0) { 281 return false; 282 } 283 284 if (bloomFilter == null) { 285 return true; 286 } 287 288 try { 289 if (!bloomFilter.supportsAutoLoading()) { 290 return true; 291 } 292 return bloomFilter.contains(row, rowOffset, rowLen, null); 293 } catch (IllegalArgumentException e) { 294 LOG.error("Bad Delete Family bloom filter data -- proceeding without", 295 e); 296 setDeleteFamilyBloomFilterFaulty(); 297 } 298 299 return true; 300 } 301 302 /** 303 * A method for checking Bloom filters. Called directly from 304 * StoreFileScanner in case of a multi-column query. 305 * 306 * @return True if passes 307 */ 308 public boolean passesGeneralRowBloomFilter(byte[] row, int rowOffset, int rowLen) { 309 BloomFilter bloomFilter = this.generalBloomFilter; 310 if (bloomFilter == null) { 311 return true; 312 } 313 314 // Used in ROW bloom 315 byte[] key = null; 316 if (rowOffset != 0 || rowLen != row.length) { 317 throw new AssertionError( 318 "For row-only Bloom filters the row " + "must occupy the whole array"); 319 } 320 key = row; 321 return checkGeneralBloomFilter(key, null, bloomFilter); 322 } 323 324 /** 325 * A method for checking Bloom filters. Called directly from 326 * StoreFileScanner in case of a multi-column query. 327 * 328 * @param cell 329 * the cell to check if present in BloomFilter 330 * @return True if passes 331 */ 332 public boolean passesGeneralRowColBloomFilter(Cell cell) { 333 BloomFilter bloomFilter = this.generalBloomFilter; 334 if (bloomFilter == null) { 335 return true; 336 } 337 // Used in ROW_COL bloom 338 Cell kvKey = null; 339 // Already if the incoming key is a fake rowcol key then use it as it is 340 if (cell.getTypeByte() == KeyValue.Type.Maximum.getCode() && cell.getFamilyLength() == 0) { 341 kvKey = cell; 342 } else { 343 kvKey = PrivateCellUtil.createFirstOnRowCol(cell); 344 } 345 return checkGeneralBloomFilter(null, kvKey, bloomFilter); 346 } 347 348 private boolean checkGeneralBloomFilter(byte[] key, Cell kvKey, BloomFilter bloomFilter) { 349 // Empty file 350 if (reader.getTrailer().getEntryCount() == 0) { 351 return false; 352 } 353 HFileBlock bloomBlock = null; 354 try { 355 boolean shouldCheckBloom; 356 ByteBuff bloom; 357 if (bloomFilter.supportsAutoLoading()) { 358 bloom = null; 359 shouldCheckBloom = true; 360 } else { 361 bloomBlock = reader.getMetaBlock(HFile.BLOOM_FILTER_DATA_KEY, true); 362 bloom = bloomBlock.getBufferWithoutHeader(); 363 shouldCheckBloom = bloom != null; 364 } 365 366 if (shouldCheckBloom) { 367 boolean exists; 368 369 // Whether the primary Bloom key is greater than the last Bloom key 370 // from the file info. For row-column Bloom filters this is not yet 371 // a sufficient condition to return false. 372 boolean keyIsAfterLast = (lastBloomKey != null); 373 // hbase:meta does not have blooms. So we need not have special interpretation 374 // of the hbase:meta cells. We can safely use Bytes.BYTES_RAWCOMPARATOR for ROW Bloom 375 if (keyIsAfterLast) { 376 if (bloomFilterType == BloomType.ROW) { 377 keyIsAfterLast = (Bytes.BYTES_RAWCOMPARATOR.compare(key, lastBloomKey) > 0); 378 } else { 379 keyIsAfterLast = (CellComparator.getInstance().compare(kvKey, lastBloomKeyOnlyKV)) > 0; 380 } 381 } 382 383 if (bloomFilterType == BloomType.ROWCOL) { 384 // Since a Row Delete is essentially a DeleteFamily applied to all 385 // columns, a file might be skipped if using row+col Bloom filter. 386 // In order to ensure this file is included an additional check is 387 // required looking only for a row bloom. 388 Cell rowBloomKey = PrivateCellUtil.createFirstOnRow(kvKey); 389 // hbase:meta does not have blooms. So we need not have special interpretation 390 // of the hbase:meta cells. We can safely use Bytes.BYTES_RAWCOMPARATOR for ROW Bloom 391 if (keyIsAfterLast 392 && (CellComparator.getInstance().compare(rowBloomKey, lastBloomKeyOnlyKV)) > 0) { 393 exists = false; 394 } else { 395 exists = 396 bloomFilter.contains(kvKey, bloom, BloomType.ROWCOL) || 397 bloomFilter.contains(rowBloomKey, bloom, BloomType.ROWCOL); 398 } 399 } else { 400 exists = !keyIsAfterLast 401 && bloomFilter.contains(key, 0, key.length, bloom); 402 } 403 404 return exists; 405 } 406 } catch (IOException e) { 407 LOG.error("Error reading bloom filter data -- proceeding without", 408 e); 409 setGeneralBloomFilterFaulty(); 410 } catch (IllegalArgumentException e) { 411 LOG.error("Bad bloom filter data -- proceeding without", e); 412 setGeneralBloomFilterFaulty(); 413 } finally { 414 // Return the bloom block so that its ref count can be decremented. 415 reader.returnBlock(bloomBlock); 416 } 417 return true; 418 } 419 420 /** 421 * Checks whether the given scan rowkey range overlaps with the current storefile's 422 * @param scan the scan specification. Used to determine the rowkey range. 423 * @return true if there is overlap, false otherwise 424 */ 425 public boolean passesKeyRangeFilter(Scan scan) { 426 Optional<Cell> firstKeyKV = this.getFirstKey(); 427 Optional<Cell> lastKeyKV = this.getLastKey(); 428 if (!firstKeyKV.isPresent() || !lastKeyKV.isPresent()) { 429 // the file is empty 430 return false; 431 } 432 if (Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW) && 433 Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) { 434 return true; 435 } 436 byte[] smallestScanRow = scan.isReversed() ? scan.getStopRow() : scan.getStartRow(); 437 byte[] largestScanRow = scan.isReversed() ? scan.getStartRow() : scan.getStopRow(); 438 boolean nonOverLapping = (getComparator() 439 .compareRows(firstKeyKV.get(), largestScanRow, 0, largestScanRow.length) > 0 && 440 !Bytes.equals(scan.isReversed() ? scan.getStartRow() : scan.getStopRow(), 441 HConstants.EMPTY_END_ROW)) || 442 getComparator().compareRows(lastKeyKV.get(), smallestScanRow, 0, 443 smallestScanRow.length) < 0; 444 return !nonOverLapping; 445 } 446 447 public Map<byte[], byte[]> loadFileInfo() throws IOException { 448 Map<byte [], byte []> fi = reader.loadFileInfo(); 449 450 byte[] b = fi.get(BLOOM_FILTER_TYPE_KEY); 451 if (b != null) { 452 bloomFilterType = BloomType.valueOf(Bytes.toString(b)); 453 } 454 455 lastBloomKey = fi.get(LAST_BLOOM_KEY); 456 if(bloomFilterType == BloomType.ROWCOL) { 457 lastBloomKeyOnlyKV = new KeyValue.KeyOnlyKeyValue(lastBloomKey, 0, lastBloomKey.length); 458 } 459 byte[] cnt = fi.get(DELETE_FAMILY_COUNT); 460 if (cnt != null) { 461 deleteFamilyCnt = Bytes.toLong(cnt); 462 } 463 464 return fi; 465 } 466 467 public void loadBloomfilter() { 468 this.loadBloomfilter(BlockType.GENERAL_BLOOM_META); 469 this.loadBloomfilter(BlockType.DELETE_FAMILY_BLOOM_META); 470 } 471 472 public void loadBloomfilter(BlockType blockType) { 473 try { 474 if (blockType == BlockType.GENERAL_BLOOM_META) { 475 if (this.generalBloomFilter != null) 476 return; // Bloom has been loaded 477 478 DataInput bloomMeta = reader.getGeneralBloomFilterMetadata(); 479 if (bloomMeta != null) { 480 // sanity check for NONE Bloom filter 481 if (bloomFilterType == BloomType.NONE) { 482 throw new IOException( 483 "valid bloom filter type not found in FileInfo"); 484 } else { 485 generalBloomFilter = BloomFilterFactory.createFromMeta(bloomMeta, 486 reader); 487 if (LOG.isTraceEnabled()) { 488 LOG.trace("Loaded " + bloomFilterType.toString() + " " 489 + generalBloomFilter.getClass().getSimpleName() 490 + " metadata for " + reader.getName()); 491 } 492 } 493 } 494 } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) { 495 if (this.deleteFamilyBloomFilter != null) 496 return; // Bloom has been loaded 497 498 DataInput bloomMeta = reader.getDeleteBloomFilterMetadata(); 499 if (bloomMeta != null) { 500 deleteFamilyBloomFilter = BloomFilterFactory.createFromMeta( 501 bloomMeta, reader); 502 LOG.info("Loaded Delete Family Bloom (" 503 + deleteFamilyBloomFilter.getClass().getSimpleName() 504 + ") metadata for " + reader.getName()); 505 } 506 } else { 507 throw new RuntimeException("Block Type: " + blockType.toString() 508 + "is not supported for Bloom filter"); 509 } 510 } catch (IOException e) { 511 LOG.error("Error reading bloom filter meta for " + blockType 512 + " -- proceeding without", e); 513 setBloomFilterFaulty(blockType); 514 } catch (IllegalArgumentException e) { 515 LOG.error("Bad bloom filter meta " + blockType 516 + " -- proceeding without", e); 517 setBloomFilterFaulty(blockType); 518 } 519 } 520 521 private void setBloomFilterFaulty(BlockType blockType) { 522 if (blockType == BlockType.GENERAL_BLOOM_META) { 523 setGeneralBloomFilterFaulty(); 524 } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) { 525 setDeleteFamilyBloomFilterFaulty(); 526 } 527 } 528 529 /** 530 * The number of Bloom filter entries in this store file, or an estimate 531 * thereof, if the Bloom filter is not loaded. This always returns an upper 532 * bound of the number of Bloom filter entries. 533 * 534 * @return an estimate of the number of Bloom filter entries in this file 535 */ 536 public long getFilterEntries() { 537 return generalBloomFilter != null ? generalBloomFilter.getKeyCount() 538 : reader.getEntries(); 539 } 540 541 public void setGeneralBloomFilterFaulty() { 542 generalBloomFilter = null; 543 } 544 545 public void setDeleteFamilyBloomFilterFaulty() { 546 this.deleteFamilyBloomFilter = null; 547 } 548 549 public Optional<Cell> getLastKey() { 550 return reader.getLastKey(); 551 } 552 553 public Optional<byte[]> getLastRowKey() { 554 return reader.getLastRowKey(); 555 } 556 557 public Optional<Cell> midKey() throws IOException { 558 return reader.midKey(); 559 } 560 561 public long length() { 562 return reader.length(); 563 } 564 565 public long getTotalUncompressedBytes() { 566 return reader.getTrailer().getTotalUncompressedBytes(); 567 } 568 569 public long getEntries() { 570 return reader.getEntries(); 571 } 572 573 public long getDeleteFamilyCnt() { 574 return deleteFamilyCnt; 575 } 576 577 public Optional<Cell> getFirstKey() { 578 return reader.getFirstKey(); 579 } 580 581 public long indexSize() { 582 return reader.indexSize(); 583 } 584 585 public BloomType getBloomFilterType() { 586 return this.bloomFilterType; 587 } 588 589 public long getSequenceID() { 590 return sequenceID; 591 } 592 593 public void setSequenceID(long sequenceID) { 594 this.sequenceID = sequenceID; 595 } 596 597 public void setBulkLoaded(boolean bulkLoadResult) { 598 this.bulkLoadResult = bulkLoadResult; 599 } 600 601 public boolean isBulkLoaded() { 602 return this.bulkLoadResult; 603 } 604 605 BloomFilter getGeneralBloomFilter() { 606 return generalBloomFilter; 607 } 608 609 long getUncompressedDataIndexSize() { 610 return reader.getTrailer().getUncompressedDataIndexSize(); 611 } 612 613 public long getTotalBloomSize() { 614 if (generalBloomFilter == null) 615 return 0; 616 return generalBloomFilter.getByteSize(); 617 } 618 619 public int getHFileVersion() { 620 return reader.getTrailer().getMajorVersion(); 621 } 622 623 public int getHFileMinorVersion() { 624 return reader.getTrailer().getMinorVersion(); 625 } 626 627 public HFile.Reader getHFileReader() { 628 return reader; 629 } 630 631 void disableBloomFilterForTesting() { 632 generalBloomFilter = null; 633 this.deleteFamilyBloomFilter = null; 634 } 635 636 public long getMaxTimestamp() { 637 return timeRange == null ? TimeRange.INITIAL_MAX_TIMESTAMP: timeRange.getMax(); 638 } 639 640 boolean isSkipResetSeqId() { 641 return skipResetSeqId; 642 } 643 644 void setSkipResetSeqId(boolean skipResetSeqId) { 645 this.skipResetSeqId = skipResetSeqId; 646 } 647}