/* * 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.decode; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellComparator; import org.apache.hadoop.hbase.CellScanner; import org.apache.hadoop.hbase.codec.prefixtree.PrefixTreeBlockMeta; import org.apache.hadoop.hbase.codec.prefixtree.decode.column.ColumnReader; import org.apache.hadoop.hbase.codec.prefixtree.decode.row.RowNodeReader; import org.apache.hadoop.hbase.codec.prefixtree.decode.timestamp.MvccVersionDecoder; import org.apache.hadoop.hbase.codec.prefixtree.decode.timestamp.TimestampDecoder; import org.apache.hadoop.hbase.codec.prefixtree.encode.other.ColumnNodeType; /** * Extends PtCell and manipulates its protected fields. Could alternatively contain a PtCell and * call get/set methods. * * This is an "Array" scanner to distinguish from a future "ByteBuffer" scanner. This * implementation requires that the bytes be in a normal java byte[] for performance. The * alternative ByteBuffer implementation would allow for accessing data in an off-heap ByteBuffer * without copying the whole buffer on-heap. */ @InterfaceAudience.Private public class PrefixTreeArrayScanner extends PrefixTreeCell implements CellScanner { /***************** fields ********************************/ protected PrefixTreeBlockMeta blockMeta; protected boolean beforeFirst; protected boolean afterLast; protected RowNodeReader[] rowNodes; protected int rowNodeStackIndex; protected RowNodeReader currentRowNode; protected ColumnReader familyReader; protected ColumnReader qualifierReader; protected ColumnReader tagsReader; protected TimestampDecoder timestampDecoder; protected MvccVersionDecoder mvccVersionDecoder; protected boolean nubCellsRemain; protected int currentCellIndex; /*********************** construct ******************************/ // pass in blockMeta so we can initialize buffers big enough for all cells in the block public PrefixTreeArrayScanner(PrefixTreeBlockMeta blockMeta, int rowTreeDepth, int rowBufferLength, int qualifierBufferLength, int tagsBufferLength) { this.rowNodes = new RowNodeReader[rowTreeDepth]; for (int i = 0; i < rowNodes.length; ++i) { rowNodes[i] = new RowNodeReader(); } this.rowBuffer = new byte[rowBufferLength]; this.familyBuffer = new byte[PrefixTreeBlockMeta.MAX_FAMILY_LENGTH]; this.familyReader = new ColumnReader(familyBuffer, ColumnNodeType.FAMILY); this.qualifierBuffer = new byte[qualifierBufferLength]; this.tagsBuffer = new byte[tagsBufferLength]; this.qualifierReader = new ColumnReader(qualifierBuffer, ColumnNodeType.QUALIFIER); this.tagsReader = new ColumnReader(tagsBuffer, ColumnNodeType.TAGS); this.timestampDecoder = new TimestampDecoder(); this.mvccVersionDecoder = new MvccVersionDecoder(); } /**************** init helpers ***************************************/ /** * Call when first accessing a block. * @return entirely new scanner if false */ public boolean areBuffersBigEnough() { if (rowNodes.length < blockMeta.getRowTreeDepth()) { return false; } if (rowBuffer.length < blockMeta.getMaxRowLength()) { return false; } if (qualifierBuffer.length < blockMeta.getMaxQualifierLength()) { return false; } if(tagsBuffer.length < blockMeta.getMaxTagsLength()) { return false; } return true; } public void initOnBlock(PrefixTreeBlockMeta blockMeta, byte[] block, boolean includeMvccVersion) { this.block = block; this.blockMeta = blockMeta; this.familyOffset = familyBuffer.length; this.familyReader.initOnBlock(blockMeta, block); this.qualifierOffset = qualifierBuffer.length; this.qualifierReader.initOnBlock(blockMeta, block); this.tagsOffset = tagsBuffer.length; this.tagsReader.initOnBlock(blockMeta, block); this.timestampDecoder.initOnBlock(blockMeta, block); this.mvccVersionDecoder.initOnBlock(blockMeta, block); this.includeMvccVersion = includeMvccVersion; resetToBeforeFirstEntry(); } // Does this have to be in the CellScanner Interface? TODO public void resetToBeforeFirstEntry() { beforeFirst = true; afterLast = false; rowNodeStackIndex = -1; currentRowNode = null; rowLength = 0; familyOffset = familyBuffer.length; familyLength = 0; qualifierOffset = blockMeta.getMaxQualifierLength(); qualifierLength = 0; nubCellsRemain = false; currentCellIndex = -1; timestamp = -1L; type = DEFAULT_TYPE; absoluteValueOffset = 0;//use 0 vs -1 so the cell is valid when value hasn't been initialized valueLength = 0;// had it at -1, but that causes null Cell to add up to the wrong length tagsOffset = blockMeta.getMaxTagsLength(); tagsLength = 0; } /** * Call this before putting the scanner back into a pool so it doesn't hold the last used block * in memory. */ public void releaseBlockReference(){ block = null; } /********************** CellScanner **********************/ @Override public Cell current() { if(isOutOfBounds()){ return null; } return (Cell)this; } /******************* Object methods ************************/ @Override public boolean equals(Object obj) { //trivial override to confirm intent (findbugs) return super.equals(obj); } @Override public int hashCode() { return super.hashCode(); } /** * Override PrefixTreeCell.toString() with a check to see if the current cell is valid. */ @Override public String toString() { Cell currentCell = current(); if(currentCell==null){ return "null"; } return ((PrefixTreeCell)currentCell).getKeyValueString(); } /******************* advance ***************************/ public boolean positionAtFirstCell() { reInitFirstNode(); return advance(); } @Override public boolean advance() { if (afterLast) { return false; } if (!hasOccurrences()) { resetToBeforeFirstEntry(); } if (beforeFirst || isLastCellInRow()) { nextRow(); if (afterLast) { return false; } } else { ++currentCellIndex; } populateNonRowFields(currentCellIndex); return true; } public boolean nextRow() { nextRowInternal(); if (afterLast) { return false; } populateNonRowFields(currentCellIndex); return true; } /** * This method is safe to call when the scanner is not on a fully valid row node, as in the case * of a row token miss in the Searcher * @return true if we are positioned on a valid row, false if past end of block */ protected boolean nextRowInternal() { if (afterLast) { return false; } if (beforeFirst) { initFirstNode(); if (currentRowNode.hasOccurrences()) { if (currentRowNode.isNub()) { nubCellsRemain = true; } currentCellIndex = 0; return true; } } if (currentRowNode.isLeaf()) { discardCurrentRowNode(true); } while (!afterLast) { if (nubCellsRemain) { nubCellsRemain = false; } if (currentRowNode.hasMoreFanNodes()) { followNextFan(); if (currentRowNode.hasOccurrences()) { // found some values currentCellIndex = 0; return true; } } else { discardCurrentRowNode(true); } } return false;// went past the end } /**************** secondary traversal methods ******************************/ protected void reInitFirstNode() { resetToBeforeFirstEntry(); initFirstNode(); } protected void initFirstNode() { int offsetIntoUnderlyingStructure = blockMeta.getAbsoluteRowOffset(); rowNodeStackIndex = 0; currentRowNode = rowNodes[0]; currentRowNode.initOnBlock(blockMeta, block, offsetIntoUnderlyingStructure); appendCurrentTokenToRowBuffer(); beforeFirst = false; } protected void followFirstFan() { followFan(0); } protected void followPreviousFan() { int nextFanPosition = currentRowNode.getFanIndex() - 1; followFan(nextFanPosition); } protected void followCurrentFan() { int currentFanPosition = currentRowNode.getFanIndex(); followFan(currentFanPosition); } protected void followNextFan() { int nextFanPosition = currentRowNode.getFanIndex() + 1; followFan(nextFanPosition); } protected void followLastFan() { followFan(currentRowNode.getLastFanIndex()); } protected void followFan(int fanIndex) { currentRowNode.setFanIndex(fanIndex); appendToRowBuffer(currentRowNode.getFanByte(fanIndex)); int nextOffsetIntoUnderlyingStructure = currentRowNode.getOffset() + currentRowNode.getNextNodeOffset(fanIndex, blockMeta); ++rowNodeStackIndex; currentRowNode = rowNodes[rowNodeStackIndex]; currentRowNode.initOnBlock(blockMeta, block, nextOffsetIntoUnderlyingStructure); //TODO getToken is spewing garbage appendCurrentTokenToRowBuffer(); if (currentRowNode.isNub()) { nubCellsRemain = true; } currentCellIndex = 0; } /** * @param forwards which marker to set if we overflow */ protected void discardCurrentRowNode(boolean forwards) { RowNodeReader rowNodeBeingPopped = currentRowNode; --rowNodeStackIndex;// pop it off the stack if (rowNodeStackIndex < 0) { currentRowNode = null; if (forwards) { markAfterLast(); } else { markBeforeFirst(); } return; } popFromRowBuffer(rowNodeBeingPopped); currentRowNode = rowNodes[rowNodeStackIndex]; } protected void markBeforeFirst() { beforeFirst = true; afterLast = false; currentRowNode = null; } protected void markAfterLast() { beforeFirst = false; afterLast = true; currentRowNode = null; } /***************** helper methods **************************/ protected void appendCurrentTokenToRowBuffer() { System.arraycopy(block, currentRowNode.getTokenArrayOffset(), rowBuffer, rowLength, currentRowNode.getTokenLength()); rowLength += currentRowNode.getTokenLength(); } protected void appendToRowBuffer(byte b) { rowBuffer[rowLength] = b; ++rowLength; } protected void popFromRowBuffer(RowNodeReader rowNodeBeingPopped) { rowLength -= rowNodeBeingPopped.getTokenLength(); --rowLength; // pop the parent's fan byte } protected boolean hasOccurrences() { return currentRowNode != null && currentRowNode.hasOccurrences(); } protected boolean isBranch() { return currentRowNode != null && !currentRowNode.hasOccurrences() && currentRowNode.hasChildren(); } protected boolean isNub() { return currentRowNode != null && currentRowNode.hasOccurrences() && currentRowNode.hasChildren(); } protected boolean isLeaf() { return currentRowNode != null && currentRowNode.hasOccurrences() && !currentRowNode.hasChildren(); } //TODO expose this in a PrefixTreeScanner interface public boolean isBeforeFirst(){ return beforeFirst; } public boolean isAfterLast(){ return afterLast; } protected boolean isOutOfBounds(){ return beforeFirst || afterLast; } protected boolean isFirstCellInRow() { return currentCellIndex == 0; } protected boolean isLastCellInRow() { return currentCellIndex == currentRowNode.getLastCellIndex(); } /********************* fill in family/qualifier/ts/type/value ************/ protected int populateNonRowFieldsAndCompareTo(int cellNum, Cell key) { populateNonRowFields(cellNum); return CellComparator.compare(this, key, true); } protected void populateFirstNonRowFields() { populateNonRowFields(0); } protected void populatePreviousNonRowFields() { populateNonRowFields(currentCellIndex - 1); } protected void populateLastNonRowFields() { populateNonRowFields(currentRowNode.getLastCellIndex()); } protected void populateNonRowFields(int cellIndex) { currentCellIndex = cellIndex; populateFamily(); populateQualifier(); // Read tags only if there are tags in the meta if(blockMeta.getNumTagsBytes() != 0) { populateTag(); } populateTimestamp(); populateMvccVersion(); populateType(); populateValueOffsets(); } protected void populateFamily() { int familyTreeIndex = currentRowNode.getFamilyOffset(currentCellIndex, blockMeta); familyOffset = familyReader.populateBuffer(familyTreeIndex).getColumnOffset(); familyLength = familyReader.getColumnLength(); } protected void populateQualifier() { int qualifierTreeIndex = currentRowNode.getColumnOffset(currentCellIndex, blockMeta); qualifierOffset = qualifierReader.populateBuffer(qualifierTreeIndex).getColumnOffset(); qualifierLength = qualifierReader.getColumnLength(); } protected void populateTag() { int tagTreeIndex = currentRowNode.getTagOffset(currentCellIndex, blockMeta); tagsOffset = tagsReader.populateBuffer(tagTreeIndex).getColumnOffset(); tagsLength = tagsReader.getColumnLength(); } protected void populateTimestamp() { if (blockMeta.isAllSameTimestamp()) { timestamp = blockMeta.getMinTimestamp(); } else { int timestampIndex = currentRowNode.getTimestampIndex(currentCellIndex, blockMeta); timestamp = timestampDecoder.getLong(timestampIndex); } } protected void populateMvccVersion() { if (blockMeta.isAllSameMvccVersion()) { mvccVersion = blockMeta.getMinMvccVersion(); } else { int mvccVersionIndex = currentRowNode.getMvccVersionIndex(currentCellIndex, blockMeta); mvccVersion = mvccVersionDecoder.getMvccVersion(mvccVersionIndex); } } protected void populateType() { int typeInt; if (blockMeta.isAllSameType()) { typeInt = blockMeta.getAllTypes(); } else { typeInt = currentRowNode.getType(currentCellIndex, blockMeta); } type = PrefixTreeCell.TYPES[typeInt]; } protected void populateValueOffsets() { int offsetIntoValueSection = currentRowNode.getValueOffset(currentCellIndex, blockMeta); absoluteValueOffset = blockMeta.getAbsoluteValueOffset() + offsetIntoValueSection; valueLength = currentRowNode.getValueLength(currentCellIndex, blockMeta); } /**************** getters ***************************/ public byte[] getTreeBytes() { return block; } public PrefixTreeBlockMeta getBlockMeta() { return blockMeta; } public int getMaxRowTreeStackNodes() { return rowNodes.length; } public int getRowBufferLength() { return rowBuffer.length; } public int getQualifierBufferLength() { return qualifierBuffer.length; } public int getTagBufferLength() { return tagsBuffer.length; } }