/** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.io; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; import org.apache.hadoop.hbase.io.hfile.CacheConfig; import org.apache.hadoop.hbase.io.hfile.HFileScanner; import org.apache.hadoop.hbase.regionserver.StoreFile; import org.apache.hadoop.hbase.util.Bytes; /** * A facade for a {@link org.apache.hadoop.hbase.io.hfile.HFile.Reader} that serves up * either the top or bottom half of a HFile where 'bottom' is the first half * of the file containing the keys that sort lowest and 'top' is the second half * of the file with keys that sort greater than those of the bottom half. * The top includes the split files midkey, of the key that follows if it does * not exist in the file. * * <p>This type works in tandem with the {@link Reference} type. This class * is used reading while Reference is used writing. * * <p>This file is not splitable. Calls to {@link #midkey()} return null. */ @InterfaceAudience.Private public class HalfStoreFileReader extends StoreFile.Reader { final Log LOG = LogFactory.getLog(HalfStoreFileReader.class); final boolean top; // This is the key we split around. Its the first possible entry on a row: // i.e. empty column and a timestamp of LATEST_TIMESTAMP. protected final byte [] splitkey; private byte[] firstKey = null; private boolean firstKeySeeked = false; /** * @param fs * @param p * @param cacheConf * @param r * @throws IOException */ public HalfStoreFileReader(final FileSystem fs, final Path p, final CacheConfig cacheConf, final Reference r, DataBlockEncoding preferredEncodingInCache) throws IOException { super(fs, p, cacheConf, preferredEncodingInCache); // This is not actual midkey for this half-file; its just border // around which we split top and bottom. Have to look in files to find // actual last and first keys for bottom and top halves. Half-files don't // have an actual midkey themselves. No midkey is how we indicate file is // not splittable. this.splitkey = r.getSplitKey(); // Is it top or bottom half? this.top = Reference.isTopFileRegion(r.getFileRegion()); } protected boolean isTop() { return this.top; } @Override public HFileScanner getScanner(final boolean cacheBlocks, final boolean pread, final boolean isCompaction) { final HFileScanner s = super.getScanner(cacheBlocks, pread, isCompaction); return new HFileScanner() { final HFileScanner delegate = s; public boolean atEnd = false; public ByteBuffer getKey() { if (atEnd) return null; return delegate.getKey(); } public String getKeyString() { if (atEnd) return null; return delegate.getKeyString(); } public ByteBuffer getValue() { if (atEnd) return null; return delegate.getValue(); } public String getValueString() { if (atEnd) return null; return delegate.getValueString(); } public KeyValue getKeyValue() { if (atEnd) return null; return delegate.getKeyValue(); } public boolean next() throws IOException { if (atEnd) return false; boolean b = delegate.next(); if (!b) { return b; } // constrain the bottom. if (!top) { ByteBuffer bb = getKey(); if (getComparator().compare(bb.array(), bb.arrayOffset(), bb.limit(), splitkey, 0, splitkey.length) >= 0) { atEnd = true; return false; } } return true; } public boolean seekBefore(byte[] key) throws IOException { return seekBefore(key, 0, key.length); } public boolean seekBefore(byte [] key, int offset, int length) throws IOException { if (top) { byte[] fk = getFirstKey(); // This will be null when the file is empty in which we can not seekBefore to any key if (fk == null) return false; if (getComparator().compare(key, offset, length, fk, 0, fk.length) <= 0) { return false; } } else { // The equals sign isn't strictly necessary just here to be consistent with seekTo if (getComparator().compare(key, offset, length, splitkey, 0, splitkey.length) >= 0) { return this.delegate.seekBefore(splitkey, 0, splitkey.length); } } return this.delegate.seekBefore(key, offset, length); } public boolean seekTo() throws IOException { if (top) { int r = this.delegate.seekTo(splitkey); if (r < 0) { // midkey is < first key in file return this.delegate.seekTo(); } if (r > 0) { return this.delegate.next(); } return true; } boolean b = delegate.seekTo(); if (!b) { return b; } // Check key. ByteBuffer k = this.delegate.getKey(); return this.delegate.getReader().getComparator(). compare(k.array(), k.arrayOffset(), k.limit(), splitkey, 0, splitkey.length) < 0; } public int seekTo(byte[] key) throws IOException { return seekTo(key, 0, key.length); } public int seekTo(byte[] key, int offset, int length) throws IOException { if (top) { if (getComparator().compare(key, offset, length, splitkey, 0, splitkey.length) < 0) { return -1; } } else { if (getComparator().compare(key, offset, length, splitkey, 0, splitkey.length) >= 0) { // we would place the scanner in the second half. // it might be an error to return false here ever... boolean res = delegate.seekBefore(splitkey, 0, splitkey.length); if (!res) { throw new IOException("Seeking for a key in bottom of file, but key exists in top of file, failed on seekBefore(midkey)"); } return 1; } } return delegate.seekTo(key, offset, length); } @Override public int reseekTo(byte[] key) throws IOException { return reseekTo(key, 0, key.length); } @Override public int reseekTo(byte[] key, int offset, int length) throws IOException { //This function is identical to the corresponding seekTo function except //that we call reseekTo (and not seekTo) on the delegate. if (top) { if (getComparator().compare(key, offset, length, splitkey, 0, splitkey.length) < 0) { return -1; } } else { if (getComparator().compare(key, offset, length, splitkey, 0, splitkey.length) >= 0) { // we would place the scanner in the second half. // it might be an error to return false here ever... boolean res = delegate.seekBefore(splitkey, 0, splitkey.length); if (!res) { throw new IOException("Seeking for a key in bottom of file, but" + " key exists in top of file, failed on seekBefore(midkey)"); } return 1; } } if (atEnd) { // skip the 'reseek' and just return 1. return 1; } return delegate.reseekTo(key, offset, length); } public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() { return this.delegate.getReader(); } public boolean isSeeked() { return this.delegate.isSeeked(); } }; } @Override public byte[] getLastKey() { if (top) { return super.getLastKey(); } // Get a scanner that caches the block and that uses pread. HFileScanner scanner = getScanner(true, true); try { if (scanner.seekBefore(this.splitkey)) { return Bytes.toBytes(scanner.getKey()); } } catch (IOException e) { LOG.warn("Failed seekBefore " + Bytes.toStringBinary(this.splitkey), e); } return null; } @Override public byte[] midkey() throws IOException { // Returns null to indicate file is not splitable. return null; } @Override public byte[] getFirstKey() { if (!firstKeySeeked) { HFileScanner scanner = getScanner(true, true, false); try { if (scanner.seekTo()) { this.firstKey = Bytes.toBytes(scanner.getKey()); } firstKeySeeked = true; } catch (IOException e) { LOG.warn("Failed seekTo first KV in the file", e); } } return this.firstKey; } }