blob: 972c3b55398f25fe21e6f43db18bc2b0352d1da9 [file] [log] [blame]
/**
*
* 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.hbase.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Scan;
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 {
private static 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;
protected final Cell splitCell;
private byte[] firstKey = null;
private boolean firstKeySeeked = false;
/**
* Creates a half file reader for a normal hfile.
* @param fs fileystem to read from
* @param p path to hfile
* @param cacheConf
* @param r original reference file (contains top or bottom)
* @param conf Configuration
* @throws IOException
*/
public HalfStoreFileReader(final FileSystem fs, final Path p,
final CacheConfig cacheConf, final Reference r, final Configuration conf)
throws IOException {
super(fs, p, cacheConf, conf);
// 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();
this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
// Is it top or bottom half?
this.top = Reference.isTopFileRegion(r.getFileRegion());
}
/**
* Creates a half file reader for a hfile referred to by an hfilelink.
* @param fs fileystem to read from
* @param p path to hfile
* @param in {@link FSDataInputStreamWrapper}
* @param size Full size of the hfile file
* @param cacheConf
* @param r original reference file (contains top or bottom)
* @param conf Configuration
* @throws IOException
*/
public HalfStoreFileReader(final FileSystem fs, final Path p, final FSDataInputStreamWrapper in,
long size, final CacheConfig cacheConf, final Reference r, final Configuration conf)
throws IOException {
super(fs, p, in, size, cacheConf, conf);
// 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();
this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
// 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 Cell 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().compareFlatKey(bb.array(), bb.arrayOffset(), bb.limit(),
splitkey, 0, splitkey.length) >= 0) {
atEnd = true;
return false;
}
}
return true;
}
@Override
public boolean seekBefore(byte[] key) throws IOException {
return seekBefore(key, 0, key.length);
}
@Override
public boolean seekBefore(byte [] key, int offset, int length)
throws IOException {
return seekBefore(new KeyValue.KeyOnlyKeyValue(key, offset, length));
}
@Override
public boolean seekTo() throws IOException {
if (top) {
int r = this.delegate.seekTo(new KeyValue.KeyOnlyKeyValue(splitkey, 0, splitkey.length));
if (r == HConstants.INDEX_KEY_MAGIC) {
return true;
}
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().
compareFlatKey(k.array(), k.arrayOffset(), k.limit(),
splitkey, 0, splitkey.length) < 0;
}
@Override
public int seekTo(byte[] key) throws IOException {
return seekTo(key, 0, key.length);
}
@Override
public int seekTo(byte[] key, int offset, int length) throws IOException {
return seekTo(new KeyValue.KeyOnlyKeyValue(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.
return reseekTo(new KeyValue.KeyOnlyKeyValue(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 int seekTo(Cell key) throws IOException {
if (top) {
if (getComparator().compareOnlyKeyPortion(key, splitCell) < 0) {
return -1;
}
} else {
if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
// we would place the scanner in the second half.
// it might be an error to return false here ever...
boolean res = delegate.seekBefore(splitCell);
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);
}
@Override
public int reseekTo(Cell key) 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().compareOnlyKeyPortion(key, splitCell) < 0) {
return -1;
}
} else {
if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
// we would place the scanner in the second half.
// it might be an error to return false here ever...
boolean res = delegate.seekBefore(splitCell);
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);
}
@Override
public boolean seekBefore(Cell key) throws IOException {
if (top) {
Cell fk = new KeyValue.KeyOnlyKeyValue(getFirstKey(), 0, getFirstKey().length);
if (fk != null && getComparator().compareOnlyKeyPortion(key, fk) <= 0) {
return false;
}
} else {
// The equals sign isn't strictly necessary just here to be consistent
// with seekTo
if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
boolean ret = this.delegate.seekBefore(splitCell);
if (ret) {
atEnd = false;
}
return ret;
}
}
boolean ret = this.delegate.seekBefore(key);
if (ret) {
atEnd = false;
}
return ret;
}
@Override
public Cell getNextIndexedKey() {
return null;
}
@Override
public void close() {
}
};
}
@Override
public boolean passesKeyRangeFilter(Scan scan) {
return true;
}
@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;
}
@Override
public long getEntries() {
// Estimate the number of entries as half the original file; this may be wildly inaccurate.
return super.getEntries() / 2;
}
@Override
public long getFilterEntries() {
// Estimate the number of entries as half the original file; this may be wildly inaccurate.
return super.getFilterEntries() / 2;
}
}