/* * 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.codec.prefixtree; import java.nio.ByteBuffer; import org.apache.hadoop.hbase.ByteBufferCell; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellComparator; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.KeyValue.Type; import org.apache.hadoop.hbase.SettableSequenceId; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.codec.prefixtree.decode.DecoderFactory; import org.apache.hadoop.hbase.codec.prefixtree.decode.PrefixTreeArraySearcher; import org.apache.hadoop.hbase.codec.prefixtree.scanner.CellScannerPosition; import org.apache.hadoop.hbase.io.HeapSize; import org.apache.hadoop.hbase.io.encoding.DataBlockEncoder.EncodedSeeker; import org.apache.hadoop.hbase.nio.ByteBuff; import org.apache.hadoop.hbase.util.ByteBufferUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ClassSize; /** * These methods have the same definition as any implementation of the EncodedSeeker. * * In the future, the EncodedSeeker could be modified to work with the Cell interface directly. It * currently returns a new KeyValue object each time getKeyValue is called. This is not horrible, * but in order to create a new KeyValue object, we must first allocate a new byte[] and copy in * the data from the PrefixTreeCell. It is somewhat heavyweight right now. */ @InterfaceAudience.Private public class PrefixTreeSeeker implements EncodedSeeker { protected boolean includeMvccVersion; protected PrefixTreeArraySearcher ptSearcher; public PrefixTreeSeeker(boolean includeMvccVersion) { this.includeMvccVersion = includeMvccVersion; } @Override public void setCurrentBuffer(ByteBuff fullBlockBuffer) { ptSearcher = DecoderFactory.checkOut(fullBlockBuffer, includeMvccVersion); rewind(); } /** * <p> * Currently unused. * </p> * TODO performance leak. should reuse the searchers. hbase does not currently have a hook where * this can be called */ public void releaseCurrentSearcher(){ DecoderFactory.checkIn(ptSearcher); } @Override public Cell getKey() { return ptSearcher.current(); } @Override public ByteBuffer getValueShallowCopy() { return CellUtil.getValueBufferShallowCopy(ptSearcher.current()); } /** * currently must do deep copy into new array */ @Override public Cell getCell() { // The PrefixTreecell is of type BytebufferedCell and the value part of the cell // determines whether we are offheap cell or onheap cell. All other parts of the cell- // row, fam and col are all represented as onheap byte[] ByteBufferCell cell = (ByteBufferCell)ptSearcher.current(); if (cell == null) { return null; } // Use the ByteBuffered cell to see if the Cell is onheap or offheap if (cell.getValueByteBuffer().hasArray()) { return new OnheapPrefixTreeCell(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength(), cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength(), cell.getValueArray(), cell.getValueOffset(), cell.getValueLength(), cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength(), cell.getTimestamp(), cell.getTypeByte(), cell.getSequenceId()); } else { return new OffheapPrefixTreeCell(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength(), cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength(), cell.getValueByteBuffer(), cell.getValuePosition(), cell.getValueLength(), cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength(), cell.getTimestamp(), cell.getTypeByte(), cell.getSequenceId()); } } /** * <p> * Currently unused. * </p><p> * A nice, lightweight reference, though the underlying cell is transient. This method may return * the same reference to the backing PrefixTreeCell repeatedly, while other implementations may * return a different reference for each Cell. * </p> * The goal will be to transition the upper layers of HBase, like Filters and KeyValueHeap, to * use this method instead of the getKeyValue() methods above. */ public Cell get() { return ptSearcher.current(); } @Override public void rewind() { ptSearcher.positionAtFirstCell(); } @Override public boolean next() { return ptSearcher.advance(); } public boolean advance() { return ptSearcher.advance(); } private static final boolean USE_POSITION_BEFORE = false; /* * Support both of these options since the underlying PrefixTree supports * both. Possibly expand the EncodedSeeker to utilize them both. */ protected int seekToOrBeforeUsingPositionAtOrBefore(Cell kv, boolean seekBefore) { // this does a deep copy of the key byte[] because the CellSearcher // interface wants a Cell CellScannerPosition position = ptSearcher.seekForwardToOrBefore(kv); if (CellScannerPosition.AT == position) { if (seekBefore) { ptSearcher.previous(); return 1; } return 0; } return 1; } protected int seekToOrBeforeUsingPositionAtOrAfter(Cell kv, boolean seekBefore) { // should probably switch this to use the seekForwardToOrBefore method CellScannerPosition position = ptSearcher.seekForwardToOrAfter(kv); if (CellScannerPosition.AT == position) { if (seekBefore) { ptSearcher.previous(); return 1; } return 0; } if (CellScannerPosition.AFTER == position) { if (!ptSearcher.isBeforeFirst()) { ptSearcher.previous(); } return 1; } if (position == CellScannerPosition.AFTER_LAST) { if (seekBefore) { ptSearcher.previous(); } return 1; } throw new RuntimeException("unexpected CellScannerPosition:" + position); } @Override public int seekToKeyInBlock(Cell key, boolean forceBeforeOnExactMatch) { if (USE_POSITION_BEFORE) { return seekToOrBeforeUsingPositionAtOrBefore(key, forceBeforeOnExactMatch); } else { return seekToOrBeforeUsingPositionAtOrAfter(key, forceBeforeOnExactMatch); } } @Override public int compareKey(CellComparator comparator, Cell key) { return comparator.compare(key, ptSearcher.current()); } /** * Cloned version of the PrefixTreeCell where except the value part, the rest * of the key part is deep copied * */ private static class OnheapPrefixTreeCell implements Cell, SettableSequenceId, HeapSize { private static final long FIXED_OVERHEAD = ClassSize.align(ClassSize.OBJECT + (5 * ClassSize.REFERENCE) + (2 * Bytes.SIZEOF_LONG) + (4 * Bytes.SIZEOF_INT) + (Bytes.SIZEOF_SHORT) + (2 * Bytes.SIZEOF_BYTE) + (5 * ClassSize.ARRAY)); private byte[] row; private short rowLength; private byte[] fam; private byte famLength; private byte[] qual; private int qualLength; private byte[] val; private int valOffset; private int valLength; private byte[] tag; private int tagsLength; private long ts; private long seqId; private byte type; public OnheapPrefixTreeCell(byte[] row, int rowOffset, short rowLength, byte[] fam, int famOffset, byte famLength, byte[] qual, int qualOffset, int qualLength, byte[] val, int valOffset, int valLength, byte[] tag, int tagOffset, int tagLength, long ts, byte type, long seqId) { this.row = new byte[rowLength]; System.arraycopy(row, rowOffset, this.row, 0, rowLength); this.rowLength = rowLength; this.fam = new byte[famLength]; System.arraycopy(fam, famOffset, this.fam, 0, famLength); this.famLength = famLength; this.qual = new byte[qualLength]; System.arraycopy(qual, qualOffset, this.qual, 0, qualLength); this.qualLength = qualLength; this.tag = new byte[tagLength]; System.arraycopy(tag, tagOffset, this.tag, 0, tagLength); this.tagsLength = tagLength; this.val = val; this.valLength = valLength; this.valOffset = valOffset; this.ts = ts; this.seqId = seqId; this.type = type; } @Override public void setSequenceId(long seqId) { this.seqId = seqId; } @Override public byte[] getRowArray() { return this.row; } @Override public int getRowOffset() { return 0; } @Override public short getRowLength() { return this.rowLength; } @Override public byte[] getFamilyArray() { return this.fam; } @Override public int getFamilyOffset() { return 0; } @Override public byte getFamilyLength() { return this.famLength; } @Override public byte[] getQualifierArray() { return this.qual; } @Override public int getQualifierOffset() { return 0; } @Override public int getQualifierLength() { return this.qualLength; } @Override public long getTimestamp() { return ts; } @Override public byte getTypeByte() { return type; } @Override public long getSequenceId() { return seqId; } @Override public byte[] getValueArray() { return val; } @Override public int getValueOffset() { return this.valOffset; } @Override public int getValueLength() { return this.valLength; } @Override public byte[] getTagsArray() { return this.tag; } @Override public int getTagsOffset() { return 0; } @Override public int getTagsLength() { return this.tagsLength; } @Override public String toString() { String row = Bytes.toStringBinary(getRowArray(), getRowOffset(), getRowLength()); String family = Bytes.toStringBinary(getFamilyArray(), getFamilyOffset(), getFamilyLength()); String qualifier = Bytes.toStringBinary(getQualifierArray(), getQualifierOffset(), getQualifierLength()); String timestamp = String.valueOf((getTimestamp())); return row + "/" + family + (family != null && family.length() > 0 ? ":" : "") + qualifier + "/" + timestamp + "/" + Type.codeToType(type); } @Override public long heapSize() { return FIXED_OVERHEAD + rowLength + famLength + qualLength + valLength + tagsLength; } } private static class OffheapPrefixTreeCell extends ByteBufferCell implements Cell, SettableSequenceId, HeapSize { private static final long FIXED_OVERHEAD = ClassSize.align(ClassSize.OBJECT + (5 * ClassSize.REFERENCE) + (2 * Bytes.SIZEOF_LONG) + (4 * Bytes.SIZEOF_INT) + (Bytes.SIZEOF_SHORT) + (2 * Bytes.SIZEOF_BYTE) + (5 * ClassSize.BYTE_BUFFER)); private ByteBuffer rowBuff; private short rowLength; private ByteBuffer famBuff; private byte famLength; private ByteBuffer qualBuff; private int qualLength; private ByteBuffer val; private int valOffset; private int valLength; private ByteBuffer tagBuff; private int tagsLength; private long ts; private long seqId; private byte type; public OffheapPrefixTreeCell(byte[] row, int rowOffset, short rowLength, byte[] fam, int famOffset, byte famLength, byte[] qual, int qualOffset, int qualLength, ByteBuffer val, int valOffset, int valLength, byte[] tag, int tagOffset, int tagLength, long ts, byte type, long seqId) { byte[] tmpRow = new byte[rowLength]; System.arraycopy(row, rowOffset, tmpRow, 0, rowLength); this.rowBuff = ByteBuffer.wrap(tmpRow); this.rowLength = rowLength; byte[] tmpFam = new byte[famLength]; System.arraycopy(fam, famOffset, tmpFam, 0, famLength); this.famBuff = ByteBuffer.wrap(tmpFam); this.famLength = famLength; byte[] tmpQual = new byte[qualLength]; System.arraycopy(qual, qualOffset, tmpQual, 0, qualLength); this.qualBuff = ByteBuffer.wrap(tmpQual); this.qualLength = qualLength; byte[] tmpTag = new byte[tagLength]; System.arraycopy(tag, tagOffset, tmpTag, 0, tagLength); this.tagBuff = ByteBuffer.wrap(tmpTag); this.tagsLength = tagLength; this.val = val; this.valLength = valLength; this.valOffset = valOffset; this.ts = ts; this.seqId = seqId; this.type = type; } @Override public void setSequenceId(long seqId) { this.seqId = seqId; } @Override public byte[] getRowArray() { return this.rowBuff.array(); } @Override public int getRowOffset() { return getRowPosition(); } @Override public short getRowLength() { return this.rowLength; } @Override public byte[] getFamilyArray() { return this.famBuff.array(); } @Override public int getFamilyOffset() { return getFamilyPosition(); } @Override public byte getFamilyLength() { return this.famLength; } @Override public byte[] getQualifierArray() { return this.qualBuff.array(); } @Override public int getQualifierOffset() { return getQualifierPosition(); } @Override public int getQualifierLength() { return this.qualLength; } @Override public long getTimestamp() { return ts; } @Override public byte getTypeByte() { return type; } @Override public long getSequenceId() { return seqId; } @Override public byte[] getValueArray() { byte[] tmpVal = new byte[valLength]; ByteBufferUtils.copyFromBufferToArray(tmpVal, val, valOffset, 0, valLength); return tmpVal; } @Override public int getValueOffset() { return 0; } @Override public int getValueLength() { return this.valLength; } @Override public byte[] getTagsArray() { return this.tagBuff.array(); } @Override public int getTagsOffset() { return getTagsPosition(); } @Override public int getTagsLength() { return this.tagsLength; } @Override public ByteBuffer getRowByteBuffer() { return this.rowBuff; } @Override public int getRowPosition() { return 0; } @Override public ByteBuffer getFamilyByteBuffer() { return this.famBuff; } @Override public int getFamilyPosition() { return 0; } @Override public ByteBuffer getQualifierByteBuffer() { return this.qualBuff; } @Override public int getQualifierPosition() { return 0; } @Override public ByteBuffer getTagsByteBuffer() { return this.tagBuff; } @Override public int getTagsPosition() { return 0; } @Override public ByteBuffer getValueByteBuffer() { return this.val; } @Override public int getValuePosition() { return this.valOffset; } @Override public long heapSize() { return FIXED_OVERHEAD; } @Override public String toString() { String row = Bytes.toStringBinary(getRowArray(), getRowOffset(), getRowLength()); String family = Bytes.toStringBinary(getFamilyArray(), getFamilyOffset(), getFamilyLength()); String qualifier = Bytes.toStringBinary(getQualifierArray(), getQualifierOffset(), getQualifierLength()); String timestamp = String.valueOf((getTimestamp())); return row + "/" + family + (family != null && family.length() > 0 ? ":" : "") + qualifier + "/" + timestamp + "/" + Type.codeToType(type); } } }