View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io.hfile;
19  
20  import java.io.DataInput;
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.classification.InterfaceAudience;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.Cell;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.KeyValue;
34  import org.apache.hadoop.hbase.KeyValue.KVComparator;
35  import org.apache.hadoop.hbase.KeyValueUtil;
36  import org.apache.hadoop.hbase.fs.HFileSystem;
37  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
38  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoder;
39  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
40  import org.apache.hadoop.hbase.io.encoding.HFileBlockDecodingContext;
41  import org.apache.hadoop.hbase.io.hfile.HFile.FileInfo;
42  import org.apache.hadoop.hbase.util.ByteBufferUtils;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.IdLock;
45  import org.apache.hadoop.io.WritableUtils;
46  import org.htrace.Trace;
47  import org.htrace.TraceScope;
48  
49  import com.google.common.annotations.VisibleForTesting;
50  
51  /**
52   * {@link HFile} reader for version 2.
53   */
54  @InterfaceAudience.Private
55  public class HFileReaderV2 extends AbstractHFileReader {
56  
57    private static final Log LOG = LogFactory.getLog(HFileReaderV2.class);
58  
59    /** Minor versions in HFile V2 starting with this number have hbase checksums */
60    public static final int MINOR_VERSION_WITH_CHECKSUM = 1;
61    /** In HFile V2 minor version that does not support checksums */
62    public static final int MINOR_VERSION_NO_CHECKSUM = 0;
63  
64    /** HFile minor version that introduced pbuf filetrailer */
65    public static final int PBUF_TRAILER_MINOR_VERSION = 2;
66  
67    /**
68     * The size of a (key length, value length) tuple that prefixes each entry in
69     * a data block.
70     */
71    public final static int KEY_VALUE_LEN_SIZE = 2 * Bytes.SIZEOF_INT;
72  
73    protected boolean includesMemstoreTS = false;
74    protected boolean decodeMemstoreTS = false;
75    protected boolean shouldIncludeMemstoreTS() {
76      return includesMemstoreTS;
77    }
78  
79    /** Filesystem-level block reader. */
80    protected HFileBlock.FSReader fsBlockReader;
81  
82    /**
83     * A "sparse lock" implementation allowing to lock on a particular block
84     * identified by offset. The purpose of this is to avoid two clients loading
85     * the same block, and have all but one client wait to get the block from the
86     * cache.
87     */
88    private IdLock offsetLock = new IdLock();
89  
90    /**
91     * Blocks read from the load-on-open section, excluding data root index, meta
92     * index, and file info.
93     */
94    private List<HFileBlock> loadOnOpenBlocks = new ArrayList<HFileBlock>();
95  
96    /** Minimum minor version supported by this HFile format */
97    static final int MIN_MINOR_VERSION = 0;
98  
99    /** Maximum minor version supported by this HFile format */
100   // We went to version 2 when we moved to pb'ing fileinfo and the trailer on
101   // the file. This version can read Writables version 1.
102   static final int MAX_MINOR_VERSION = 3;
103 
104   /** Minor versions starting with this number have faked index key */
105   static final int MINOR_VERSION_WITH_FAKED_KEY = 3;
106 
107   protected HFileContext hfileContext;
108 
109   /**
110    * Opens a HFile. You must load the index before you can use it by calling
111    * {@link #loadFileInfo()}.
112    *
113    * @param path Path to HFile.
114    * @param trailer File trailer.
115    * @param fsdis input stream.
116    * @param size Length of the stream.
117    * @param cacheConf Cache configuration.
118    * @param hfs
119    * @param conf
120    */
121   public HFileReaderV2(final Path path, final FixedFileTrailer trailer,
122       final FSDataInputStreamWrapper fsdis, final long size, final CacheConfig cacheConf,
123       final HFileSystem hfs, final Configuration conf) throws IOException {
124     super(path, trailer, size, cacheConf, hfs, conf);
125     this.conf = conf;
126     trailer.expectMajorVersion(getMajorVersion());
127     validateMinorVersion(path, trailer.getMinorVersion());
128     this.hfileContext = createHFileContext(fsdis, fileSize, hfs, path, trailer);
129     HFileBlock.FSReaderV2 fsBlockReaderV2 = new HFileBlock.FSReaderV2(fsdis, fileSize, hfs, path,
130         hfileContext);
131     this.fsBlockReader = fsBlockReaderV2; // upcast
132 
133     // Comparator class name is stored in the trailer in version 2.
134     comparator = trailer.createComparator();
135     dataBlockIndexReader = new HFileBlockIndex.BlockIndexReader(comparator,
136         trailer.getNumDataIndexLevels(), this);
137     metaBlockIndexReader = new HFileBlockIndex.BlockIndexReader(
138         KeyValue.RAW_COMPARATOR, 1);
139 
140     // Parse load-on-open data.
141 
142     HFileBlock.BlockIterator blockIter = fsBlockReaderV2.blockRange(
143         trailer.getLoadOnOpenDataOffset(),
144         fileSize - trailer.getTrailerSize());
145 
146     // Data index. We also read statistics about the block index written after
147     // the root level.
148     dataBlockIndexReader.readMultiLevelIndexRoot(
149         blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX),
150         trailer.getDataIndexCount());
151 
152     // Meta index.
153     metaBlockIndexReader.readRootIndex(
154         blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX),
155         trailer.getMetaIndexCount());
156 
157     // File info
158     fileInfo = new FileInfo();
159     fileInfo.read(blockIter.nextBlockWithBlockType(BlockType.FILE_INFO).getByteStream());
160     lastKey = fileInfo.get(FileInfo.LASTKEY);
161     avgKeyLen = Bytes.toInt(fileInfo.get(FileInfo.AVG_KEY_LEN));
162     avgValueLen = Bytes.toInt(fileInfo.get(FileInfo.AVG_VALUE_LEN));
163     byte [] keyValueFormatVersion =
164         fileInfo.get(HFileWriterV2.KEY_VALUE_VERSION);
165     includesMemstoreTS = keyValueFormatVersion != null &&
166         Bytes.toInt(keyValueFormatVersion) ==
167             HFileWriterV2.KEY_VALUE_VER_WITH_MEMSTORE;
168     fsBlockReaderV2.setIncludesMemstoreTS(includesMemstoreTS);
169     if (includesMemstoreTS) {
170       decodeMemstoreTS = Bytes.toLong(fileInfo.get(HFileWriterV2.MAX_MEMSTORE_TS_KEY)) > 0;
171     }
172 
173     // Read data block encoding algorithm name from file info.
174     dataBlockEncoder = HFileDataBlockEncoderImpl.createFromFileInfo(fileInfo);
175     fsBlockReaderV2.setDataBlockEncoder(dataBlockEncoder);
176 
177     // Store all other load-on-open blocks for further consumption.
178     HFileBlock b;
179     while ((b = blockIter.nextBlock()) != null) {
180       loadOnOpenBlocks.add(b);
181     }
182 
183     // Prefetch file blocks upon open if requested
184     if (cacheConf.shouldPrefetchOnOpen()) {
185       PrefetchExecutor.request(path, new Runnable() {
186         public void run() {
187           try {
188             long offset = 0;
189             long end = fileSize - getTrailer().getTrailerSize();
190             HFileBlock prevBlock = null;
191             while (offset < end) {
192               if (Thread.interrupted()) {
193                 break;
194               }
195               long onDiskSize = -1;
196               if (prevBlock != null) {
197                 onDiskSize = prevBlock.getNextBlockOnDiskSizeWithHeader();
198               }
199               HFileBlock block = readBlock(offset, onDiskSize, true, false, false, false,
200                 null, null);
201               prevBlock = block;
202               offset += block.getOnDiskSizeWithHeader();
203             }
204           } catch (IOException e) {
205             // IOExceptions are probably due to region closes (relocation, etc.)
206             if (LOG.isTraceEnabled()) {
207               LOG.trace("Exception encountered while prefetching " + path + ":", e);
208             }
209           } catch (Exception e) {
210             // Other exceptions are interesting
211             LOG.warn("Exception encountered while prefetching " + path + ":", e);
212           } finally {
213             PrefetchExecutor.complete(path);
214           }
215         }
216       });
217     }
218   }
219 
220   protected HFileContext createHFileContext(FSDataInputStreamWrapper fsdis, long fileSize,
221       HFileSystem hfs, Path path, FixedFileTrailer trailer) throws IOException {
222     return new HFileContextBuilder()
223       .withIncludesMvcc(this.includesMemstoreTS)
224       .withCompression(this.compressAlgo)
225       .withHBaseCheckSum(trailer.getMinorVersion() >= MINOR_VERSION_WITH_CHECKSUM)
226       .build();
227   }
228 
229   /**
230    * Create a Scanner on this file. No seeks or reads are done on creation. Call
231    * {@link HFileScanner#seekTo(byte[])} to position an start the read. There is
232    * nothing to clean up in a Scanner. Letting go of your references to the
233    * scanner is sufficient.
234    *
235    * @param cacheBlocks True if we should cache blocks read in by this scanner.
236    * @param pread Use positional read rather than seek+read if true (pread is
237    *          better for random reads, seek+read is better scanning).
238    * @param isCompaction is scanner being used for a compaction?
239    * @return Scanner on this file.
240    */
241    @Override
242    public HFileScanner getScanner(boolean cacheBlocks, final boolean pread,
243       final boolean isCompaction) {
244     if (dataBlockEncoder.useEncodedScanner()) {
245       return new EncodedScannerV2(this, cacheBlocks, pread, isCompaction,
246           hfileContext);
247     }
248 
249     return new ScannerV2(this, cacheBlocks, pread, isCompaction);
250   }
251 
252    private HFileBlock getCachedBlock(BlockCacheKey cacheKey, boolean cacheBlock, boolean useLock,
253        boolean isCompaction, boolean updateCacheMetrics, BlockType expectedBlockType,
254        DataBlockEncoding expectedDataBlockEncoding) throws IOException {
255      // Check cache for block. If found return.
256      if (cacheConf.isBlockCacheEnabled()) {
257        BlockCache cache = cacheConf.getBlockCache();
258        HFileBlock cachedBlock = (HFileBlock) cache.getBlock(cacheKey, cacheBlock, useLock,
259          updateCacheMetrics);
260        if (cachedBlock != null) {
261          validateBlockType(cachedBlock, expectedBlockType);
262 
263          if (expectedDataBlockEncoding == null) {
264            return cachedBlock;
265          }
266          DataBlockEncoding actualDataBlockEncoding =
267                  cachedBlock.getDataBlockEncoding();
268          // Block types other than data blocks always have
269          // DataBlockEncoding.NONE. To avoid false negative cache misses, only
270          // perform this check if cached block is a data block.
271          if (cachedBlock.getBlockType().isData() &&
272                  !actualDataBlockEncoding.equals(expectedDataBlockEncoding)) {
273            // This mismatch may happen if a ScannerV2, which is used for say a
274            // compaction, tries to read an encoded block from the block cache.
275            // The reverse might happen when an EncodedScannerV2 tries to read
276            // un-encoded blocks which were cached earlier.
277            //
278            // Because returning a data block with an implicit BlockType mismatch
279            // will cause the requesting scanner to throw a disk read should be
280            // forced here. This will potentially cause a significant number of
281            // cache misses, so update so we should keep track of this as it might
282            // justify the work on a CompoundScannerV2.
283            if (!expectedDataBlockEncoding.equals(DataBlockEncoding.NONE) &&
284                    !actualDataBlockEncoding.equals(DataBlockEncoding.NONE)) {
285              // If the block is encoded but the encoding does not match the
286              // expected encoding it is likely the encoding was changed but the
287              // block was not yet evicted. Evictions on file close happen async
288              // so blocks with the old encoding still linger in cache for some
289              // period of time. This event should be rare as it only happens on
290              // schema definition change.
291              LOG.info("Evicting cached block with key " + cacheKey +
292                      " because of a data block encoding mismatch" +
293                      "; expected: " + expectedDataBlockEncoding +
294                      ", actual: " + actualDataBlockEncoding);
295              cache.evictBlock(cacheKey);
296            }
297            return null;
298          }
299          return cachedBlock;
300        }
301      }
302      return null;
303    }
304   /**
305    * @param metaBlockName
306    * @param cacheBlock Add block to cache, if found
307    * @return block wrapped in a ByteBuffer, with header skipped
308    * @throws IOException
309    */
310   @Override
311   public ByteBuffer getMetaBlock(String metaBlockName, boolean cacheBlock)
312       throws IOException {
313     if (trailer.getMetaIndexCount() == 0) {
314       return null; // there are no meta blocks
315     }
316     if (metaBlockIndexReader == null) {
317       throw new IOException("Meta index not loaded");
318     }
319 
320     byte[] mbname = Bytes.toBytes(metaBlockName);
321     int block = metaBlockIndexReader.rootBlockContainingKey(mbname,
322         0, mbname.length);
323     if (block == -1)
324       return null;
325     long blockSize = metaBlockIndexReader.getRootBlockDataSize(block);
326     long startTimeNs = System.nanoTime();
327 
328     // Per meta key from any given file, synchronize reads for said block. This
329     // is OK to do for meta blocks because the meta block index is always
330     // single-level.
331     synchronized (metaBlockIndexReader.getRootBlockKey(block)) {
332       // Check cache for block. If found return.
333       long metaBlockOffset = metaBlockIndexReader.getRootBlockOffset(block);
334       BlockCacheKey cacheKey = new BlockCacheKey(name, metaBlockOffset);
335 
336       cacheBlock &= cacheConf.shouldCacheDataOnRead();
337       if (cacheConf.isBlockCacheEnabled()) {
338         HFileBlock cachedBlock = getCachedBlock(cacheKey, cacheBlock, false, true, true,
339           BlockType.META, null);
340         if (cachedBlock != null) {
341           // Return a distinct 'shallow copy' of the block,
342           // so pos does not get messed by the scanner
343           return cachedBlock.getBufferWithoutHeader();
344         }
345         // Cache Miss, please load.
346       }
347 
348       HFileBlock metaBlock = fsBlockReader.readBlockData(metaBlockOffset,
349           blockSize, -1, true);
350 
351       final long delta = System.nanoTime() - startTimeNs;
352       HFile.offerReadLatency(delta, true);
353 
354       // Cache the block
355       if (cacheBlock) {
356         cacheConf.getBlockCache().cacheBlock(cacheKey, metaBlock,
357             cacheConf.isInMemory(), this.cacheConf.isCacheDataInL1());
358       }
359 
360       return metaBlock.getBufferWithoutHeader();
361     }
362   }
363 
364   /**
365    * Read in a file block of the given {@link BlockType} and
366    * {@link DataBlockEncoding}.
367    * @param dataBlockOffset offset to read.
368    * @param onDiskBlockSize size of the block
369    * @param cacheBlock
370    * @param pread Use positional read instead of seek+read (positional is
371    *          better doing random reads whereas seek+read is better scanning).
372    * @param isCompaction is this block being read as part of a compaction
373    * @param expectedBlockType the block type we are expecting to read with this
374    *          read operation, or null to read whatever block type is available
375    *          and avoid checking (that might reduce caching efficiency of
376    *          encoded data blocks)
377    * @param expectedDataBlockEncoding the data block encoding the caller is
378    *          expecting data blocks to be in, or null to not perform this
379    *          check and return the block irrespective of the encoding. This
380    *          check only applies to data blocks and can be set to null when
381    *          the caller is expecting to read a non-data block and has set
382    *          expectedBlockType accordingly.
383    * @return Block wrapped in a ByteBuffer.
384    * @throws IOException
385    */
386   @Override
387   public HFileBlock readBlock(long dataBlockOffset, long onDiskBlockSize,
388       final boolean cacheBlock, boolean pread, final boolean isCompaction,
389       boolean updateCacheMetrics, BlockType expectedBlockType,
390       DataBlockEncoding expectedDataBlockEncoding)
391       throws IOException {
392     if (dataBlockIndexReader == null) {
393       throw new IOException("Block index not loaded");
394     }
395     if (dataBlockOffset < 0
396         || dataBlockOffset >= trailer.getLoadOnOpenDataOffset()) {
397       throw new IOException("Requested block is out of range: "
398           + dataBlockOffset + ", lastDataBlockOffset: "
399           + trailer.getLastDataBlockOffset());
400     }
401     // For any given block from any given file, synchronize reads for said
402     // block.
403     // Without a cache, this synchronizing is needless overhead, but really
404     // the other choice is to duplicate work (which the cache would prevent you
405     // from doing).
406 
407     BlockCacheKey cacheKey =
408         new BlockCacheKey(name, dataBlockOffset);
409 
410     boolean useLock = false;
411     IdLock.Entry lockEntry = null;
412     TraceScope traceScope = Trace.startSpan("HFileReaderV2.readBlock");
413     try {
414       while (true) {
415         if (useLock) {
416           lockEntry = offsetLock.getLockEntry(dataBlockOffset);
417         }
418 
419         // Check cache for block. If found return.
420         if (cacheConf.isBlockCacheEnabled()) {
421           // Try and get the block from the block cache. If the useLock variable is true then this
422           // is the second time through the loop and it should not be counted as a block cache miss.
423           HFileBlock cachedBlock = getCachedBlock(cacheKey, cacheBlock, useLock, isCompaction,
424             updateCacheMetrics, expectedBlockType, expectedDataBlockEncoding);
425           if (cachedBlock != null) {
426             validateBlockType(cachedBlock, expectedBlockType);
427             if (cachedBlock.getBlockType().isData()) {
428               if (updateCacheMetrics) {
429                 HFile.dataBlockReadCnt.incrementAndGet();
430               }
431               // Validate encoding type for data blocks. We include encoding
432               // type in the cache key, and we expect it to match on a cache hit.
433               if (cachedBlock.getDataBlockEncoding() != dataBlockEncoder.getDataBlockEncoding()) {
434                 throw new IOException("Cached block under key " + cacheKey + " "
435                   + "has wrong encoding: " + cachedBlock.getDataBlockEncoding() + " (expected: "
436                   + dataBlockEncoder.getDataBlockEncoding() + ")");
437               }
438             }
439             return cachedBlock;
440           }
441           // Carry on, please load.
442         }
443         if (!useLock) {
444           // check cache again with lock
445           useLock = true;
446           continue;
447         }
448         if (Trace.isTracing()) {
449           traceScope.getSpan().addTimelineAnnotation("blockCacheMiss");
450         }
451         // Load block from filesystem.
452         long startTimeNs = System.nanoTime();
453         HFileBlock hfileBlock = fsBlockReader.readBlockData(dataBlockOffset, onDiskBlockSize, -1,
454             pread);
455         validateBlockType(hfileBlock, expectedBlockType);
456 
457         final long delta = System.nanoTime() - startTimeNs;
458         HFile.offerReadLatency(delta, pread);
459 
460         // Cache the block if necessary
461         if (cacheBlock && cacheConf.shouldCacheBlockOnRead(hfileBlock.getBlockType().getCategory())) {
462           cacheConf.getBlockCache().cacheBlock(cacheKey, hfileBlock, cacheConf.isInMemory(),
463             this.cacheConf.isCacheDataInL1());
464         }
465 
466         if (updateCacheMetrics && hfileBlock.getBlockType().isData()) {
467           HFile.dataBlockReadCnt.incrementAndGet();
468         }
469 
470         return hfileBlock;
471       }
472     } finally {
473       traceScope.close();
474       if (lockEntry != null) {
475         offsetLock.releaseLockEntry(lockEntry);
476       }
477     }
478   }
479 
480   @Override
481   public boolean hasMVCCInfo() {
482     return includesMemstoreTS && decodeMemstoreTS;
483   }
484 
485   /**
486    * Compares the actual type of a block retrieved from cache or disk with its
487    * expected type and throws an exception in case of a mismatch. Expected
488    * block type of {@link BlockType#DATA} is considered to match the actual
489    * block type [@link {@link BlockType#ENCODED_DATA} as well.
490    * @param block a block retrieved from cache or disk
491    * @param expectedBlockType the expected block type, or null to skip the
492    *          check
493    */
494   private void validateBlockType(HFileBlock block,
495       BlockType expectedBlockType) throws IOException {
496     if (expectedBlockType == null) {
497       return;
498     }
499     BlockType actualBlockType = block.getBlockType();
500     if (expectedBlockType.isData() && actualBlockType.isData()) {
501       // We consider DATA to match ENCODED_DATA for the purpose of this
502       // verification.
503       return;
504     }
505     if (actualBlockType != expectedBlockType) {
506       throw new IOException("Expected block type " + expectedBlockType + ", " +
507           "but got " + actualBlockType + ": " + block);
508     }
509   }
510 
511   /**
512    * @return Last key in the file. May be null if file has no entries. Note that
513    *         this is not the last row key, but rather the byte form of the last
514    *         KeyValue.
515    */
516   @Override
517   public byte[] getLastKey() {
518     return dataBlockIndexReader.isEmpty() ? null : lastKey;
519   }
520 
521   /**
522    * @return Midkey for this file. We work with block boundaries only so
523    *         returned midkey is an approximation only.
524    * @throws IOException
525    */
526   @Override
527   public byte[] midkey() throws IOException {
528     return dataBlockIndexReader.midkey();
529   }
530 
531   @Override
532   public void close() throws IOException {
533     close(cacheConf.shouldEvictOnClose());
534   }
535 
536   public void close(boolean evictOnClose) throws IOException {
537     PrefetchExecutor.cancel(path);
538     if (evictOnClose && cacheConf.isBlockCacheEnabled()) {
539       int numEvicted = cacheConf.getBlockCache().evictBlocksByHfileName(name);
540       if (LOG.isTraceEnabled()) {
541         LOG.trace("On close, file=" + name + " evicted=" + numEvicted
542           + " block(s)");
543       }
544     }
545     fsBlockReader.closeStreams();
546   }
547 
548   public DataBlockEncoding getEffectiveEncodingInCache(boolean isCompaction) {
549     return dataBlockEncoder.getEffectiveEncodingInCache(isCompaction);
550   }
551 
552   /** For testing */
553   @Override
554   HFileBlock.FSReader getUncachedBlockReader() {
555     return fsBlockReader;
556   }
557 
558 
559   protected abstract static class AbstractScannerV2
560       extends AbstractHFileReader.Scanner {
561     protected HFileBlock block;
562 
563     /**
564      * The next indexed key is to keep track of the indexed key of the next data block.
565      * If the nextIndexedKey is HConstants.NO_NEXT_INDEXED_KEY, it means that the
566      * current data block is the last data block.
567      *
568      * If the nextIndexedKey is null, it means the nextIndexedKey has not been loaded yet.
569      */
570     protected byte[] nextIndexedKey;
571 
572     public AbstractScannerV2(HFileReaderV2 r, boolean cacheBlocks,
573         final boolean pread, final boolean isCompaction) {
574       super(r, cacheBlocks, pread, isCompaction);
575     }
576 
577     protected abstract ByteBuffer getFirstKeyInBlock(HFileBlock curBlock);
578 
579     protected abstract int loadBlockAndSeekToKey(HFileBlock seekToBlock, byte[] nextIndexedKey,
580         boolean rewind, Cell key, boolean seekBefore) throws IOException;
581 
582     @Override
583     public int seekTo(byte[] key, int offset, int length) throws IOException {
584       // Always rewind to the first key of the block, because the given key
585       // might be before or after the current key.
586       return seekTo(new KeyValue.KeyOnlyKeyValue(key, offset, length));
587     }
588 
589     @Override
590     public int reseekTo(byte[] key, int offset, int length) throws IOException {
591       return reseekTo(new KeyValue.KeyOnlyKeyValue(key, offset, length));
592     }
593 
594     @Override
595     public int seekTo(Cell key) throws IOException {
596       return seekTo(key, true);
597     }
598 
599     @Override
600     public int reseekTo(Cell key) throws IOException {
601       int compared;
602       if (isSeeked()) {
603         compared = compareKey(reader.getComparator(), key);
604         if (compared < 1) {
605           // If the required key is less than or equal to current key, then
606           // don't do anything.
607           return compared;
608         } else {
609           // The comparison with no_next_index_key has to be checked
610           if (this.nextIndexedKey != null &&
611               (this.nextIndexedKey == HConstants.NO_NEXT_INDEXED_KEY || reader
612               .getComparator()
613                   .compareOnlyKeyPortion(key,
614                       new KeyValue.KeyOnlyKeyValue(nextIndexedKey, 0, 
615                           nextIndexedKey.length)) < 0)) {
616             // The reader shall continue to scan the current data block instead
617             // of querying the
618             // block index as long as it knows the target key is strictly
619             // smaller than
620             // the next indexed key or the current data block is the last data
621             // block.
622             return loadBlockAndSeekToKey(this.block, nextIndexedKey, false, key, false);
623           }
624         }
625       }
626       // Don't rewind on a reseek operation, because reseek implies that we are
627       // always going forward in the file.
628       return seekTo(key, false);
629     }
630 
631 
632     /**
633      * An internal API function. Seek to the given key, optionally rewinding to
634      * the first key of the block before doing the seek.
635      *
636      * @param key - a cell representing the key that we need to fetch
637      * @param rewind whether to rewind to the first key of the block before
638      *        doing the seek. If this is false, we are assuming we never go
639      *        back, otherwise the result is undefined.
640      * @return -1 if the key is earlier than the first key of the file,
641      *         0 if we are at the given key, 1 if we are past the given key
642      *         -2 if the key is earlier than the first key of the file while
643      *         using a faked index key
644      * @throws IOException
645      */
646     public int seekTo(Cell key, boolean rewind) throws IOException {
647       HFileBlockIndex.BlockIndexReader indexReader = reader.getDataBlockIndexReader();
648       BlockWithScanInfo blockWithScanInfo = indexReader.loadDataBlockWithScanInfo(key, block,
649           cacheBlocks, pread, isCompaction, getEffectiveDataBlockEncoding());
650       if (blockWithScanInfo == null || blockWithScanInfo.getHFileBlock() == null) {
651         // This happens if the key e.g. falls before the beginning of the file.
652         return -1;
653       }
654       return loadBlockAndSeekToKey(blockWithScanInfo.getHFileBlock(),
655           blockWithScanInfo.getNextIndexedKey(), rewind, key, false);
656     }
657 
658     @Override
659     public boolean seekBefore(byte[] key, int offset, int length) throws IOException {
660       return seekBefore(new KeyValue.KeyOnlyKeyValue(key, offset, length));
661     }
662 
663     @Override
664     public boolean seekBefore(Cell key) throws IOException {
665       HFileBlock seekToBlock = reader.getDataBlockIndexReader().seekToDataBlock(key, block,
666           cacheBlocks, pread, isCompaction,
667           ((HFileReaderV2) reader).getEffectiveEncodingInCache(isCompaction));
668       if (seekToBlock == null) {
669         return false;
670       }
671       ByteBuffer firstKey = getFirstKeyInBlock(seekToBlock);
672 
673       if (reader.getComparator()
674           .compareOnlyKeyPortion(
675               new KeyValue.KeyOnlyKeyValue(firstKey.array(), firstKey.arrayOffset(),
676                   firstKey.limit()), key) >= 0) {
677         long previousBlockOffset = seekToBlock.getPrevBlockOffset();
678         // The key we are interested in
679         if (previousBlockOffset == -1) {
680           // we have a 'problem', the key we want is the first of the file.
681           return false;
682         }
683 
684         // It is important that we compute and pass onDiskSize to the block
685         // reader so that it does not have to read the header separately to
686         // figure out the size.
687         seekToBlock = reader.readBlock(previousBlockOffset,
688             seekToBlock.getOffset() - previousBlockOffset, cacheBlocks,
689             pread, isCompaction, true, BlockType.DATA, getEffectiveDataBlockEncoding());
690         // TODO shortcut: seek forward in this block to the last key of the
691         // block.
692       }
693       byte[] firstKeyInCurrentBlock = Bytes.getBytes(firstKey);
694       loadBlockAndSeekToKey(seekToBlock, firstKeyInCurrentBlock, true, key, true);
695       return true;
696     }
697 
698     /**
699      * Scans blocks in the "scanned" section of the {@link HFile} until the next
700      * data block is found.
701      *
702      * @return the next block, or null if there are no more data blocks
703      * @throws IOException
704      */
705     protected HFileBlock readNextDataBlock() throws IOException {
706       long lastDataBlockOffset = reader.getTrailer().getLastDataBlockOffset();
707       if (block == null)
708         return null;
709 
710       HFileBlock curBlock = block;
711 
712       do {
713         if (curBlock.getOffset() >= lastDataBlockOffset)
714           return null;
715 
716         if (curBlock.getOffset() < 0) {
717           throw new IOException("Invalid block file offset: " + block);
718         }
719 
720         // We are reading the next block without block type validation, because
721         // it might turn out to be a non-data block.
722         curBlock = reader.readBlock(curBlock.getOffset()
723             + curBlock.getOnDiskSizeWithHeader(),
724             curBlock.getNextBlockOnDiskSizeWithHeader(), cacheBlocks, pread,
725             isCompaction, true, null, getEffectiveDataBlockEncoding());
726       } while (!curBlock.getBlockType().isData());
727 
728       return curBlock;
729     }
730     
731     public DataBlockEncoding getEffectiveDataBlockEncoding() {
732       return ((HFileReaderV2)reader).getEffectiveEncodingInCache(isCompaction);
733     }
734     /**
735      * Compare the given key against the current key
736      * @param comparator
737      * @param key
738      * @param offset
739      * @param length
740      * @return -1 is the passed key is smaller than the current key, 0 if equal and 1 if greater
741      */
742     public abstract int compareKey(KVComparator comparator, byte[] key, int offset,
743         int length);
744 
745     public abstract int compareKey(KVComparator comparator, Cell kv);
746   }
747 
748   /**
749    * Implementation of {@link HFileScanner} interface.
750    */
751   protected static class ScannerV2 extends AbstractScannerV2 {
752     private HFileReaderV2 reader;
753 
754     public ScannerV2(HFileReaderV2 r, boolean cacheBlocks,
755         final boolean pread, final boolean isCompaction) {
756       super(r, cacheBlocks, pread, isCompaction);
757       this.reader = r;
758     }
759 
760     @Override
761     public Cell getKeyValue() {
762       if (!isSeeked())
763         return null;
764 
765       KeyValue ret = new KeyValue(blockBuffer.array(), blockBuffer.arrayOffset()
766           + blockBuffer.position(), getCellBufSize());
767       if (this.reader.shouldIncludeMemstoreTS()) {
768         ret.setMvccVersion(currMemstoreTS);
769       }
770       return ret;
771     }
772 
773     protected int getCellBufSize() {
774       return KEY_VALUE_LEN_SIZE + currKeyLen + currValueLen;
775     }
776 
777     @Override
778     public ByteBuffer getKey() {
779       assertSeeked();
780       return ByteBuffer.wrap(
781           blockBuffer.array(),
782           blockBuffer.arrayOffset() + blockBuffer.position()
783               + KEY_VALUE_LEN_SIZE, currKeyLen).slice();
784     }
785 
786     @Override
787     public int compareKey(KVComparator comparator, byte[] key, int offset, int length) {
788       return comparator.compareFlatKey(key, offset, length, blockBuffer.array(),
789           blockBuffer.arrayOffset() + blockBuffer.position() + KEY_VALUE_LEN_SIZE, currKeyLen);
790     }
791 
792     @Override
793     public ByteBuffer getValue() {
794       assertSeeked();
795       return ByteBuffer.wrap(
796           blockBuffer.array(),
797           blockBuffer.arrayOffset() + blockBuffer.position()
798               + KEY_VALUE_LEN_SIZE + currKeyLen, currValueLen).slice();
799     }
800 
801     protected void setNonSeekedState() {
802       block = null;
803       blockBuffer = null;
804       currKeyLen = 0;
805       currValueLen = 0;
806       currMemstoreTS = 0;
807       currMemstoreTSLen = 0;
808     }
809 
810     /**
811      * Go to the next key/value in the block section. Loads the next block if
812      * necessary. If successful, {@link #getKey()} and {@link #getValue()} can
813      * be called.
814      *
815      * @return true if successfully navigated to the next key/value
816      */
817     @Override
818     public boolean next() throws IOException {
819       assertSeeked();
820 
821       try {
822         blockBuffer.position(getNextCellStartPosition());
823       } catch (IllegalArgumentException e) {
824         LOG.error("Current pos = " + blockBuffer.position()
825             + "; currKeyLen = " + currKeyLen + "; currValLen = "
826             + currValueLen + "; block limit = " + blockBuffer.limit()
827             + "; HFile name = " + reader.getName()
828             + "; currBlock currBlockOffset = " + block.getOffset());
829         throw e;
830       }
831 
832       if (blockBuffer.remaining() <= 0) {
833         long lastDataBlockOffset =
834             reader.getTrailer().getLastDataBlockOffset();
835 
836         if (block.getOffset() >= lastDataBlockOffset) {
837           setNonSeekedState();
838           return false;
839         }
840 
841         // read the next block
842         HFileBlock nextBlock = readNextDataBlock();
843         if (nextBlock == null) {
844           setNonSeekedState();
845           return false;
846         }
847 
848         updateCurrBlock(nextBlock);
849         return true;
850       }
851 
852       // We are still in the same block.
853       readKeyValueLen();
854       return true;
855     }
856 
857     protected int getNextCellStartPosition() {
858       return blockBuffer.position() + KEY_VALUE_LEN_SIZE + currKeyLen + currValueLen
859           + currMemstoreTSLen;
860     }
861 
862     /**
863      * Positions this scanner at the start of the file.
864      *
865      * @return false if empty file; i.e. a call to next would return false and
866      *         the current key and value are undefined.
867      * @throws IOException
868      */
869     @Override
870     public boolean seekTo() throws IOException {
871       if (reader == null) {
872         return false;
873       }
874 
875       if (reader.getTrailer().getEntryCount() == 0) {
876         // No data blocks.
877         return false;
878       }
879 
880       long firstDataBlockOffset =
881           reader.getTrailer().getFirstDataBlockOffset();
882       if (block != null && block.getOffset() == firstDataBlockOffset) {
883         blockBuffer.rewind();
884         readKeyValueLen();
885         return true;
886       }
887 
888       block = reader.readBlock(firstDataBlockOffset, -1, cacheBlocks, pread,
889           isCompaction, true, BlockType.DATA, getEffectiveDataBlockEncoding());
890       if (block.getOffset() < 0) {
891         throw new IOException("Invalid block offset: " + block.getOffset());
892       }
893       updateCurrBlock(block);
894       return true;
895     }
896 
897     @Override
898     protected int loadBlockAndSeekToKey(HFileBlock seekToBlock, byte[] nextIndexedKey,
899         boolean rewind, Cell key, boolean seekBefore) throws IOException {
900       if (block == null || block.getOffset() != seekToBlock.getOffset()) {
901         updateCurrBlock(seekToBlock);
902       } else if (rewind) {
903         blockBuffer.rewind();
904       }
905 
906       // Update the nextIndexedKey
907       this.nextIndexedKey = nextIndexedKey;
908       return blockSeek(key, seekBefore);
909     }
910 
911     /**
912      * Updates the current block to be the given {@link HFileBlock}. Seeks to
913      * the the first key/value pair.
914      *
915      * @param newBlock the block to make current
916      */
917     protected void updateCurrBlock(HFileBlock newBlock) {
918       block = newBlock;
919 
920       // sanity check
921       if (block.getBlockType() != BlockType.DATA) {
922         throw new IllegalStateException("ScannerV2 works only on data " +
923             "blocks, got " + block.getBlockType() + "; " +
924             "fileName=" + reader.name + ", " +
925             "dataBlockEncoder=" + reader.dataBlockEncoder + ", " +
926             "isCompaction=" + isCompaction);
927       }
928 
929       blockBuffer = block.getBufferWithoutHeader();
930       readKeyValueLen();
931       blockFetches++;
932 
933       // Reset the next indexed key
934       this.nextIndexedKey = null;
935     }
936 
937     protected void readKeyValueLen() {
938       blockBuffer.mark();
939       currKeyLen = blockBuffer.getInt();
940       currValueLen = blockBuffer.getInt();
941       ByteBufferUtils.skip(blockBuffer, currKeyLen + currValueLen);
942       readMvccVersion();
943       if (currKeyLen < 0 || currValueLen < 0
944           || currKeyLen > blockBuffer.limit()
945           || currValueLen > blockBuffer.limit()) {
946         throw new IllegalStateException("Invalid currKeyLen " + currKeyLen
947             + " or currValueLen " + currValueLen + ". Block offset: "
948             + block.getOffset() + ", block length: " + blockBuffer.limit()
949             + ", position: " + blockBuffer.position() + " (without header).");
950       }
951       blockBuffer.reset();
952     }
953 
954     protected void readMvccVersion() {
955       if (this.reader.shouldIncludeMemstoreTS()) {
956         if (this.reader.decodeMemstoreTS) {
957           try {
958             currMemstoreTS = Bytes.readVLong(blockBuffer.array(), blockBuffer.arrayOffset()
959                 + blockBuffer.position());
960             currMemstoreTSLen = WritableUtils.getVIntSize(currMemstoreTS);
961           } catch (Exception e) {
962             throw new RuntimeException("Error reading memstore timestamp", e);
963           }
964         } else {
965           currMemstoreTS = 0;
966           currMemstoreTSLen = 1;
967         }
968       }
969     }
970 
971     /**
972      * Within a loaded block, seek looking for the last key that is smaller than
973      * (or equal to?) the key we are interested in.
974      *
975      * A note on the seekBefore: if you have seekBefore = true, AND the first
976      * key in the block = key, then you'll get thrown exceptions. The caller has
977      * to check for that case and load the previous block as appropriate.
978      *
979      * @param key
980      *          the key to find
981      * @param seekBefore
982      *          find the key before the given key in case of exact match.
983      * @return 0 in case of an exact key match, 1 in case of an inexact match,
984      *         -2 in case of an inexact match and furthermore, the input key
985      *         less than the first key of current block(e.g. using a faked index
986      *         key)
987      */
988     protected int blockSeek(Cell key, boolean seekBefore) {
989       int klen, vlen;
990       long memstoreTS = 0;
991       int memstoreTSLen = 0;
992       int lastKeyValueSize = -1;
993       KeyValue.KeyOnlyKeyValue keyOnlykv = new KeyValue.KeyOnlyKeyValue();
994       do {
995         blockBuffer.mark();
996         klen = blockBuffer.getInt();
997         vlen = blockBuffer.getInt();
998         blockBuffer.reset();
999         if (this.reader.shouldIncludeMemstoreTS()) {
1000           if (this.reader.decodeMemstoreTS) {
1001             try {
1002               int memstoreTSOffset = blockBuffer.arrayOffset() + blockBuffer.position()
1003                   + KEY_VALUE_LEN_SIZE + klen + vlen;
1004               memstoreTS = Bytes.readVLong(blockBuffer.array(), memstoreTSOffset);
1005               memstoreTSLen = WritableUtils.getVIntSize(memstoreTS);
1006             } catch (Exception e) {
1007               throw new RuntimeException("Error reading memstore timestamp", e);
1008             }
1009           } else {
1010             memstoreTS = 0;
1011             memstoreTSLen = 1;
1012           }
1013         }
1014 
1015         int keyOffset = blockBuffer.arrayOffset() + blockBuffer.position() + KEY_VALUE_LEN_SIZE;
1016         keyOnlykv.setKey(blockBuffer.array(), keyOffset, klen);
1017         int comp = reader.getComparator().compareOnlyKeyPortion(key, keyOnlykv);
1018 
1019         if (comp == 0) {
1020           if (seekBefore) {
1021             if (lastKeyValueSize < 0) {
1022               KeyValue kv = KeyValueUtil.ensureKeyValue(key);
1023               throw new IllegalStateException("blockSeek with seekBefore "
1024                   + "at the first key of the block: key="
1025                   + Bytes.toStringBinary(kv.getKey(), kv.getKeyOffset(), kv.getKeyLength())
1026                   + ", blockOffset=" + block.getOffset() + ", onDiskSize="
1027                   + block.getOnDiskSizeWithHeader());
1028             }
1029             blockBuffer.position(blockBuffer.position() - lastKeyValueSize);
1030             readKeyValueLen();
1031             return 1; // non exact match.
1032           }
1033           currKeyLen = klen;
1034           currValueLen = vlen;
1035           if (this.reader.shouldIncludeMemstoreTS()) {
1036             currMemstoreTS = memstoreTS;
1037             currMemstoreTSLen = memstoreTSLen;
1038           }
1039           return 0; // indicate exact match
1040         } else if (comp < 0) {
1041           if (lastKeyValueSize > 0)
1042             blockBuffer.position(blockBuffer.position() - lastKeyValueSize);
1043           readKeyValueLen();
1044           if (lastKeyValueSize == -1 && blockBuffer.position() == 0
1045               && this.reader.trailer.getMinorVersion() >= MINOR_VERSION_WITH_FAKED_KEY) {
1046             return HConstants.INDEX_KEY_MAGIC;
1047           }
1048           return 1;
1049         }
1050 
1051         // The size of this key/value tuple, including key/value length fields.
1052         lastKeyValueSize = klen + vlen + memstoreTSLen + KEY_VALUE_LEN_SIZE;
1053         blockBuffer.position(blockBuffer.position() + lastKeyValueSize);
1054       } while (blockBuffer.remaining() > 0);
1055 
1056       // Seek to the last key we successfully read. This will happen if this is
1057       // the last key/value pair in the file, in which case the following call
1058       // to next() has to return false.
1059       blockBuffer.position(blockBuffer.position() - lastKeyValueSize);
1060       readKeyValueLen();
1061       return 1; // didn't exactly find it.
1062     }
1063 
1064     @Override
1065     protected ByteBuffer getFirstKeyInBlock(HFileBlock curBlock) {
1066       ByteBuffer buffer = curBlock.getBufferWithoutHeader();
1067       // It is safe to manipulate this buffer because we own the buffer object.
1068       buffer.rewind();
1069       int klen = buffer.getInt();
1070       buffer.getInt();
1071       ByteBuffer keyBuff = buffer.slice();
1072       keyBuff.limit(klen);
1073       keyBuff.rewind();
1074       return keyBuff;
1075     }
1076 
1077     @Override
1078     public String getKeyString() {
1079       return Bytes.toStringBinary(blockBuffer.array(),
1080           blockBuffer.arrayOffset() + blockBuffer.position()
1081               + KEY_VALUE_LEN_SIZE, currKeyLen);
1082     }
1083 
1084     @Override
1085     public String getValueString() {
1086       return Bytes.toString(blockBuffer.array(), blockBuffer.arrayOffset()
1087           + blockBuffer.position() + KEY_VALUE_LEN_SIZE + currKeyLen,
1088           currValueLen);
1089     }
1090 
1091     @Override
1092     public int compareKey(KVComparator comparator, Cell key) {
1093       return comparator.compareOnlyKeyPortion(
1094           key,
1095           new KeyValue.KeyOnlyKeyValue(blockBuffer.array(), blockBuffer.arrayOffset()
1096               + blockBuffer.position() + KEY_VALUE_LEN_SIZE, currKeyLen));
1097     }
1098   }
1099 
1100   /**
1101    * ScannerV2 that operates on encoded data blocks.
1102    */
1103   protected static class EncodedScannerV2 extends AbstractScannerV2 {
1104     private final HFileBlockDecodingContext decodingCtx;
1105     private final DataBlockEncoder.EncodedSeeker seeker;
1106     private final DataBlockEncoder dataBlockEncoder;
1107     protected final HFileContext meta;
1108 
1109     public EncodedScannerV2(HFileReaderV2 reader, boolean cacheBlocks,
1110         boolean pread, boolean isCompaction, HFileContext meta) {
1111       super(reader, cacheBlocks, pread, isCompaction);
1112       DataBlockEncoding encoding = reader.dataBlockEncoder.getDataBlockEncoding();
1113       dataBlockEncoder = encoding.getEncoder();
1114       decodingCtx = dataBlockEncoder.newDataBlockDecodingContext(meta);
1115       seeker = dataBlockEncoder.createSeeker(
1116         reader.getComparator(), decodingCtx);
1117       this.meta = meta;
1118     }
1119 
1120     @Override
1121     public boolean isSeeked(){
1122       return this.block != null;
1123     }
1124 
1125     /**
1126      * Updates the current block to be the given {@link HFileBlock}. Seeks to
1127      * the the first key/value pair.
1128      *
1129      * @param newBlock the block to make current
1130      * @throws CorruptHFileException
1131      */
1132     private void updateCurrentBlock(HFileBlock newBlock) throws CorruptHFileException {
1133       block = newBlock;
1134 
1135       // sanity checks
1136       if (block.getBlockType() != BlockType.ENCODED_DATA) {
1137         throw new IllegalStateException(
1138             "EncodedScanner works only on encoded data blocks");
1139       }
1140       short dataBlockEncoderId = block.getDataBlockEncodingId();
1141       if (!DataBlockEncoding.isCorrectEncoder(dataBlockEncoder, dataBlockEncoderId)) {
1142         String encoderCls = dataBlockEncoder.getClass().getName();
1143         throw new CorruptHFileException("Encoder " + encoderCls
1144           + " doesn't support data block encoding "
1145           + DataBlockEncoding.getNameFromId(dataBlockEncoderId));
1146       }
1147 
1148       seeker.setCurrentBuffer(getEncodedBuffer(newBlock));
1149       blockFetches++;
1150 
1151       // Reset the next indexed key
1152       this.nextIndexedKey = null;
1153     }
1154 
1155     private ByteBuffer getEncodedBuffer(HFileBlock newBlock) {
1156       ByteBuffer origBlock = newBlock.getBufferReadOnly();
1157       ByteBuffer encodedBlock = ByteBuffer.wrap(origBlock.array(),
1158           origBlock.arrayOffset() + newBlock.headerSize() +
1159           DataBlockEncoding.ID_SIZE,
1160           newBlock.getUncompressedSizeWithoutHeader() -
1161           DataBlockEncoding.ID_SIZE).slice();
1162       return encodedBlock;
1163     }
1164 
1165     @Override
1166     public boolean seekTo() throws IOException {
1167       if (reader == null) {
1168         return false;
1169       }
1170 
1171       if (reader.getTrailer().getEntryCount() == 0) {
1172         // No data blocks.
1173         return false;
1174       }
1175 
1176       long firstDataBlockOffset =
1177           reader.getTrailer().getFirstDataBlockOffset();
1178       if (block != null && block.getOffset() == firstDataBlockOffset) {
1179         seeker.rewind();
1180         return true;
1181       }
1182 
1183       block = reader.readBlock(firstDataBlockOffset, -1, cacheBlocks, pread,
1184           isCompaction, true, BlockType.DATA, getEffectiveDataBlockEncoding());
1185       if (block.getOffset() < 0) {
1186         throw new IOException("Invalid block offset: " + block.getOffset());
1187       }
1188       updateCurrentBlock(block);
1189       return true;
1190     }
1191 
1192     @Override
1193     public boolean next() throws IOException {
1194       boolean isValid = seeker.next();
1195       if (!isValid) {
1196         block = readNextDataBlock();
1197         isValid = block != null;
1198         if (isValid) {
1199           updateCurrentBlock(block);
1200         }
1201       }
1202       return isValid;
1203     }
1204 
1205     @Override
1206     public ByteBuffer getKey() {
1207       assertValidSeek();
1208       return seeker.getKeyDeepCopy();
1209     }
1210 
1211     @Override
1212     public int compareKey(KVComparator comparator, byte[] key, int offset, int length) {
1213       return seeker.compareKey(comparator, key, offset, length);
1214     }
1215 
1216     @Override
1217     public ByteBuffer getValue() {
1218       assertValidSeek();
1219       return seeker.getValueShallowCopy();
1220     }
1221 
1222     @Override
1223     public Cell getKeyValue() {
1224       if (block == null) {
1225         return null;
1226       }
1227       return seeker.getKeyValue();
1228     }
1229 
1230     @Override
1231     public String getKeyString() {
1232       ByteBuffer keyBuffer = getKey();
1233       return Bytes.toStringBinary(keyBuffer.array(),
1234           keyBuffer.arrayOffset(), keyBuffer.limit());
1235     }
1236 
1237     @Override
1238     public String getValueString() {
1239       ByteBuffer valueBuffer = getValue();
1240       return Bytes.toStringBinary(valueBuffer.array(),
1241           valueBuffer.arrayOffset(), valueBuffer.limit());
1242     }
1243 
1244     private void assertValidSeek() {
1245       if (block == null) {
1246         throw new NotSeekedException();
1247       }
1248     }
1249 
1250     @Override
1251     protected ByteBuffer getFirstKeyInBlock(HFileBlock curBlock) {
1252       return dataBlockEncoder.getFirstKeyInBlock(getEncodedBuffer(curBlock));
1253     }
1254 
1255     @Override
1256     protected int loadBlockAndSeekToKey(HFileBlock seekToBlock, byte[] nextIndexedKey,
1257         boolean rewind, Cell key, boolean seekBefore) throws IOException {
1258       if (block == null || block.getOffset() != seekToBlock.getOffset()) {
1259         updateCurrentBlock(seekToBlock);
1260       } else if (rewind) {
1261         seeker.rewind();
1262       }
1263       this.nextIndexedKey = nextIndexedKey;
1264       return seeker.seekToKeyInBlock(key, seekBefore);
1265     }
1266 
1267     @Override
1268     public int compareKey(KVComparator comparator, Cell key) {
1269       return seeker.compareKey(comparator, key);
1270     }
1271   }
1272 
1273   /**
1274    * Returns a buffer with the Bloom filter metadata. The caller takes
1275    * ownership of the buffer.
1276    */
1277   @Override
1278   public DataInput getGeneralBloomFilterMetadata() throws IOException {
1279     return this.getBloomFilterMetadata(BlockType.GENERAL_BLOOM_META);
1280   }
1281 
1282   @Override
1283   public DataInput getDeleteBloomFilterMetadata() throws IOException {
1284     return this.getBloomFilterMetadata(BlockType.DELETE_FAMILY_BLOOM_META);
1285   }
1286 
1287   private DataInput getBloomFilterMetadata(BlockType blockType)
1288   throws IOException {
1289     if (blockType != BlockType.GENERAL_BLOOM_META &&
1290         blockType != BlockType.DELETE_FAMILY_BLOOM_META) {
1291       throw new RuntimeException("Block Type: " + blockType.toString() +
1292           " is not supported") ;
1293     }
1294 
1295     for (HFileBlock b : loadOnOpenBlocks)
1296       if (b.getBlockType() == blockType)
1297         return b.getByteStream();
1298     return null;
1299   }
1300 
1301   @Override
1302   public boolean isFileInfoLoaded() {
1303     return true; // We load file info in constructor in version 2.
1304   }
1305 
1306   /**
1307    * Validates that the minor version is within acceptable limits.
1308    * Otherwise throws an Runtime exception
1309    */
1310   private void validateMinorVersion(Path path, int minorVersion) {
1311     if (minorVersion < MIN_MINOR_VERSION ||
1312         minorVersion > MAX_MINOR_VERSION) {
1313       String msg = "Minor version for path " + path + 
1314                    " is expected to be between " +
1315                    MIN_MINOR_VERSION + " and " + MAX_MINOR_VERSION +
1316                    " but is found to be " + minorVersion;
1317       LOG.error(msg);
1318       throw new RuntimeException(msg);
1319     }
1320   }
1321 
1322   @Override
1323   public int getMajorVersion() {
1324     return 2;
1325   }
1326 
1327   @Override
1328   public HFileContext getFileContext() {
1329     return hfileContext;
1330   }
1331 
1332   /**
1333    * Returns false if block prefetching was requested for this file and has
1334    * not completed, true otherwise
1335    */
1336   @VisibleForTesting
1337   boolean prefetchComplete() {
1338     return PrefetchExecutor.isCompleted(path);
1339   }
1340 }