/*
* 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.cassandra.db;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.apache.cassandra.db.columniterator.IdentityQueryFilter;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.dht.*;
/**
* Groups key range and column filter for range queries.
*
* The main "trick" of this class is that the column filter can only
* be obtained by providing the row key on which the column filter will
* be applied (which we always know before actually querying the columns).
*
* This allows the paging DataRange to return a filter for most rows but a
* potentially different ones for the starting and stopping key. Could
* allow more fancy stuff in the future too, like column filters that
* depend on the actual key value :)
*/
public class DataRange
{
private final AbstractBounds<RowPosition> keyRange;
protected IDiskAtomFilter columnFilter;
protected final boolean selectFullRow;
public DataRange(AbstractBounds<RowPosition> range, IDiskAtomFilter columnFilter)
{
this.keyRange = range;
this.columnFilter = columnFilter;
this.selectFullRow = columnFilter instanceof SliceQueryFilter
? isFullRowSlice((SliceQueryFilter)columnFilter)
: false;
}
public static boolean isFullRowSlice(SliceQueryFilter filter)
{
return filter.slices.length == 1
&& filter.start().remaining() == 0
&& filter.finish().remaining() == 0
&& filter.count == Integer.MAX_VALUE;
}
public static DataRange allData(IPartitioner partitioner)
{
return forKeyRange(new Range<Token>(partitioner.getMinimumToken(), partitioner.getMinimumToken()));
}
public static DataRange forKeyRange(Range<Token> keyRange)
{
return new DataRange(keyRange.toRowBounds(), new IdentityQueryFilter());
}
public AbstractBounds<RowPosition> keyRange()
{
return keyRange;
}
public RowPosition startKey()
{
return keyRange.left;
}
public RowPosition stopKey()
{
return keyRange.right;
}
// Whether the bounds of this DataRange actually wraps around.
public boolean isWrapAround()
{
// On range can ever wrap
return keyRange instanceof Range && ((Range)keyRange).isWrapAround();
}
public boolean contains(RowPosition pos)
{
return keyRange.contains(pos);
}
public int getLiveCount(ColumnFamily data, long now)
{
return columnFilter instanceof SliceQueryFilter
? ((SliceQueryFilter)columnFilter).lastCounted()
: columnFilter.getLiveCount(data, now);
}
public boolean selectsFullRowFor(ByteBuffer rowKey)
{
return selectFullRow;
}
public IDiskAtomFilter columnFilter(ByteBuffer rowKey)
{
return columnFilter;
}
public void updateColumnsLimit(int count)
{
columnFilter.updateColumnsLimit(count);
}
public static class Paging extends DataRange
{
private final SliceQueryFilter sliceFilter;
private final Comparator<ByteBuffer> comparator;
private final ByteBuffer columnStart;
private final ByteBuffer columnFinish;
private Paging(AbstractBounds<RowPosition> range, SliceQueryFilter filter, ByteBuffer columnStart, ByteBuffer columnFinish, Comparator<ByteBuffer> comparator)
{
super(range, filter);
// When using a paging range, we don't allow wrapped ranges, as it's unclear how to handle them properly.
// This is ok for now since we only need this in range slice queries, and the range are "unwrapped" in that case.
assert !(range instanceof Range) || !((Range)range).isWrapAround() || range.right.isMinimum() : range;
this.sliceFilter = filter;
this.comparator = comparator;
this.columnStart = columnStart;
this.columnFinish = columnFinish;
}
public Paging(AbstractBounds<RowPosition> range, SliceQueryFilter filter, ByteBuffer columnStart, ByteBuffer columnFinish, AbstractType<?> comparator)
{
this(range, filter, columnStart, columnFinish, filter.isReversed() ? comparator.reverseComparator : comparator);
}
@Override
public boolean selectsFullRowFor(ByteBuffer rowKey)
{
// If we initial filter is not the full filter, don't bother
if (!selectFullRow)
return false;
if (!equals(startKey(), rowKey) && !equals(stopKey(), rowKey))
return selectFullRow;
return isFullRowSlice((SliceQueryFilter)columnFilter(rowKey));
}
private boolean equals(RowPosition pos, ByteBuffer rowKey)
{
return pos instanceof DecoratedKey && ((DecoratedKey)pos).key.equals(rowKey);
}
@Override
public IDiskAtomFilter columnFilter(ByteBuffer rowKey)
{
/*
* We have that ugly hack that for slice queries, when we ask for
* the live count, we reach into the query filter to get the last
* counter number of columns to avoid recounting.
* Maybe we should just remove that hack, but in the meantime, we
* need to keep a reference the last returned filter.
*/
columnFilter = equals(startKey(), rowKey) || equals(stopKey(), rowKey)
? sliceFilter.withUpdatedSlices(slicesForKey(rowKey))
: sliceFilter;
return columnFilter;
}
private ColumnSlice[] slicesForKey(ByteBuffer key)
{
// We don't call that until it's necessary, so assume we have to do some hard work
ByteBuffer newStart = equals(startKey(), key) ? columnStart : null;
ByteBuffer newFinish = equals(stopKey(), key) ? columnFinish : null;
List<ColumnSlice> newSlices = new ArrayList<ColumnSlice>(sliceFilter.slices.length); // in the common case, we'll have the same number of slices
for (ColumnSlice slice : sliceFilter.slices)
{
if (newStart != null)
{
if (slice.isBefore(comparator, newStart))
continue; // we skip that slice
if (slice.includes(comparator, newStart))
slice = new ColumnSlice(newStart, slice.finish);
// Whether we've updated the slice or not, we don't have to bother about newStart anymore
newStart = null;
}
assert newStart == null;
if (newFinish != null && !slice.isBefore(comparator, newFinish))
{
if (slice.includes(comparator, newFinish))
newSlices.add(new ColumnSlice(slice.start, newFinish));
// In any case, we're done
break;
}
newSlices.add(slice);
}
return newSlices.toArray(new ColumnSlice[newSlices.size()]);
}
@Override
public void updateColumnsLimit(int count)
{
columnFilter.updateColumnsLimit(count);
sliceFilter.updateColumnsLimit(count);
}
}
}