package org.apache.cassandra.db.filter; /* * * 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. * */ import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import com.google.common.collect.AbstractIterator; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.IColumn; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.io.IndexHelper; import org.apache.cassandra.io.SSTableReader; import org.apache.cassandra.io.util.FileDataInput; /** * A Column Iterator over SSTable */ class SSTableSliceIterator extends AbstractIterator<IColumn> implements ColumnIterator { private final boolean reversed; private final byte[] startColumn; private final byte[] finishColumn; private final AbstractType comparator; private ColumnGroupReader reader; public SSTableSliceIterator(SSTableReader ssTable, String key, byte[] startColumn, byte[] finishColumn, boolean reversed) throws IOException { this.reversed = reversed; /* Morph key into actual key based on the partition type. */ DecoratedKey decoratedKey = ssTable.getPartitioner().decorateKey(key); FileDataInput fdi = ssTable.getFileDataInput(decoratedKey, DatabaseDescriptor.getSlicedReadBufferSizeInKB() * 1024); this.comparator = ssTable.getColumnComparator(); this.startColumn = startColumn; this.finishColumn = finishColumn; if (fdi != null) reader = new ColumnGroupReader(ssTable, decoratedKey, fdi); } private boolean isColumnNeeded(IColumn column) { if (startColumn.length == 0 && finishColumn.length == 0) return true; else if (startColumn.length == 0 && !reversed) return comparator.compare(column.name(), finishColumn) <= 0; else if (startColumn.length == 0 && reversed) return comparator.compare(column.name(), finishColumn) >= 0; else if (finishColumn.length == 0 && !reversed) return comparator.compare(column.name(), startColumn) >= 0; else if (finishColumn.length == 0 && reversed) return comparator.compare(column.name(), startColumn) <= 0; else if (!reversed) return comparator.compare(column.name(), startColumn) >= 0 && comparator.compare(column.name(), finishColumn) <= 0; else // if reversed return comparator.compare(column.name(), startColumn) <= 0 && comparator.compare(column.name(), finishColumn) >= 0; } public ColumnFamily getColumnFamily() { return reader == null ? null : reader.getEmptyColumnFamily(); } protected IColumn computeNext() { if (reader == null) return endOfData(); while (true) { IColumn column = reader.pollColumn(); if (column == null) return endOfData(); if (isColumnNeeded(column)) return column; } } public void close() throws IOException { if (reader != null) reader.close(); } /** * This is a reader that finds the block for a starting column and returns * blocks before/after it for each next call. This function assumes that * the CF is sorted by name and exploits the name index. */ class ColumnGroupReader { private final ColumnFamily emptyColumnFamily; private final List<IndexHelper.IndexInfo> indexes; private final FileDataInput file; private int curRangeIndex; private Deque<IColumn> blockColumns = new ArrayDeque<IColumn>(); public ColumnGroupReader(SSTableReader ssTable, DecoratedKey key, FileDataInput input) throws IOException { this.file = input; DecoratedKey keyInDisk = ssTable.getPartitioner().convertFromDiskFormat(file.readUTF()); assert keyInDisk.equals(key) : String.format("%s != %s in %s", keyInDisk, key, file.getPath()); file.readInt(); // row size IndexHelper.skipBloomFilter(file); indexes = IndexHelper.deserializeIndex(file); emptyColumnFamily = ColumnFamily.serializer().deserializeFromSSTableNoColumns(ssTable.makeColumnFamily(), file); file.readInt(); // column count file.mark(); curRangeIndex = IndexHelper.indexFor(startColumn, indexes, comparator, reversed); } public ColumnFamily getEmptyColumnFamily() { return emptyColumnFamily; } public IColumn pollColumn() { IColumn column = blockColumns.poll(); if (column == null) { try { if (getNextBlock()) column = blockColumns.poll(); } catch (IOException e) { throw new RuntimeException(e); } } return column; } public boolean getNextBlock() throws IOException { if (curRangeIndex < 0 || curRangeIndex >= indexes.size()) return false; /* seek to the correct offset to the data, and calculate the data size */ IndexHelper.IndexInfo curColPosition = indexes.get(curRangeIndex); /* see if this read is really necessary. */ if (reversed) { if ((finishColumn.length > 0 && comparator.compare(finishColumn, curColPosition.lastName) > 0) || (startColumn.length > 0 && comparator.compare(startColumn, curColPosition.firstName) < 0)) return false; } else { if ((startColumn.length > 0 && comparator.compare(startColumn, curColPosition.lastName) > 0) || (finishColumn.length > 0 && comparator.compare(finishColumn, curColPosition.firstName) < 0)) return false; } boolean outOfBounds = false; file.reset(); long curOffset = file.skipBytes((int) curColPosition.offset); assert curOffset == curColPosition.offset; while (file.bytesPastMark() < curColPosition.offset + curColPosition.width && !outOfBounds) { IColumn column = emptyColumnFamily.getColumnSerializer().deserialize(file); if (reversed) blockColumns.addFirst(column); else blockColumns.addLast(column); /* see if we can stop seeking. */ if (!reversed && finishColumn.length > 0) outOfBounds = comparator.compare(column.name(), finishColumn) >= 0; else if (reversed && startColumn.length > 0) outOfBounds = comparator.compare(column.name(), startColumn) >= 0; if (outOfBounds) break; } if (reversed) curRangeIndex--; else curRangeIndex++; return true; } public void close() throws IOException { file.close(); } } }