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}