mirror of https://github.com/apache/cassandra
Clean up KeyRangeIterator classes
* replace KeyRangeConcatIterator's PriorityQeueu with List * remove KeyRangeIterator.current and simplify * remove injected exception and tests - not relevant to the current implementation * expand randomized testing * inline getCurrent() -> peek(); rename getCount to getMaxKeys * redefine skipTo contract to not return a value (which saves unnecessary work when skipTo is called multiple times in a row) * calling hasNext in skipTo is a pessimization; if the iterator is in DONE state, then skipTo will see it and avoid further effort; if it is not, then we are computing a next value that we're just going to throw away * fix SingleNodeQueryFailureTest - tests now multi and single index * rationalize/standardize the way we release SSTableIndexes in QueryController patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe, Ekaterina Dimitrova for CASSANDRA-19428 Co-authored-by: Caleb Rackliffe <calebrackliffe@gmail.com> Co-authored-by: Jonathan Ellis <jbellis@apache.org> Co-authored-by: Piotr Kolaczkowski <pkolaczk@datastax.com> Co-authored-by: Michael Marshall <michael.marshall@datastax.com>
This commit is contained in:
parent
973aad7b68
commit
b0150e86fb
|
|
@ -1,4 +1,5 @@
|
|||
5.0-beta2
|
||||
* Clean up KeyRangeIterator classes (CASSANDRA-19428)
|
||||
* Warn clients about possible consistency violations for filtering queries against multiple mutable columns (CASSANDRA-19489)
|
||||
* Align buffer with commitlog segment size (CASSANDRA-19471)
|
||||
* Ensure SAI indexes empty byte buffers for types that allow them as a valid input (CASSANDRA-19461)
|
||||
|
|
|
|||
|
|
@ -41,17 +41,12 @@ public class IndexSearchResultIterator extends KeyRangeIterator
|
|||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(IndexSearchResultIterator.class);
|
||||
|
||||
private final QueryContext context;
|
||||
private final KeyRangeIterator union;
|
||||
private final Collection<SSTableIndex> referencedIndexes;
|
||||
|
||||
private IndexSearchResultIterator(KeyRangeIterator union, Collection<SSTableIndex> referencedIndexes, QueryContext queryContext)
|
||||
private IndexSearchResultIterator(KeyRangeIterator union, Runnable onClose)
|
||||
{
|
||||
super(union.getMinimum(), union.getMaximum(), union.getCount());
|
||||
|
||||
super(union.getMinimum(), union.getMaximum(), union.getMaxKeys(), onClose);
|
||||
this.union = union;
|
||||
this.referencedIndexes = referencedIndexes;
|
||||
this.context = queryContext;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,7 +57,8 @@ public class IndexSearchResultIterator extends KeyRangeIterator
|
|||
Collection<SSTableIndex> sstableIndexes,
|
||||
AbstractBounds<PartitionPosition> keyRange,
|
||||
QueryContext queryContext,
|
||||
boolean includeMemtables)
|
||||
boolean includeMemtables,
|
||||
Runnable onClose)
|
||||
{
|
||||
List<KeyRangeIterator> subIterators = new ArrayList<>(sstableIndexes.size() + (includeMemtables ? 1 : 0));
|
||||
|
||||
|
|
@ -97,24 +93,25 @@ public class IndexSearchResultIterator extends KeyRangeIterator
|
|||
}
|
||||
}
|
||||
|
||||
KeyRangeIterator union = KeyRangeUnionIterator.build(subIterators);
|
||||
return new IndexSearchResultIterator(union, sstableIndexes, queryContext);
|
||||
KeyRangeIterator union = KeyRangeUnionIterator.build(subIterators, () -> {});
|
||||
return new IndexSearchResultIterator(union, onClose);
|
||||
}
|
||||
|
||||
public static IndexSearchResultIterator build(List<KeyRangeIterator> sstableIntersections,
|
||||
KeyRangeIterator memtableResults,
|
||||
Set<SSTableIndex> referencedIndexes,
|
||||
QueryContext queryContext)
|
||||
QueryContext queryContext,
|
||||
Runnable onClose)
|
||||
{
|
||||
queryContext.sstablesHit += referencedIndexes
|
||||
.stream()
|
||||
.map(SSTableIndex::getSSTable).collect(Collectors.toSet()).size();
|
||||
queryContext.checkpoint();
|
||||
KeyRangeIterator union = KeyRangeUnionIterator.builder(sstableIntersections.size() + 1)
|
||||
KeyRangeIterator union = KeyRangeUnionIterator.builder(sstableIntersections.size() + 1, () -> {})
|
||||
.add(sstableIntersections)
|
||||
.add(memtableResults)
|
||||
.build();
|
||||
return new IndexSearchResultIterator(union, referencedIndexes, queryContext);
|
||||
return new IndexSearchResultIterator(union, onClose);
|
||||
}
|
||||
|
||||
protected PrimaryKey computeNext()
|
||||
|
|
@ -127,22 +124,10 @@ public class IndexSearchResultIterator extends KeyRangeIterator
|
|||
union.skipTo(nextKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
super.close();
|
||||
FileUtils.closeQuietly(union);
|
||||
referencedIndexes.forEach(IndexSearchResultIterator::releaseQuietly);
|
||||
referencedIndexes.clear();
|
||||
}
|
||||
|
||||
private static void releaseQuietly(SSTableIndex index)
|
||||
{
|
||||
try
|
||||
{
|
||||
index.release();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error(index.getIndexIdentifier().logMessage(String.format("Failed to release index on SSTable %s", index.getSSTable())), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ public class PostingListRangeIterator extends KeyRangeIterator
|
|||
PrimaryKeyMap primaryKeyMap,
|
||||
IndexSegmentSearcherContext searcherContext)
|
||||
{
|
||||
super(searcherContext.minimumKey, searcherContext.maximumKey, searcherContext.count());
|
||||
super(searcherContext.minimumKey, searcherContext.maximumKey, searcherContext.count(), () -> {});
|
||||
|
||||
this.indexIdentifier = indexIdentifier;
|
||||
this.primaryKeyMap = primaryKeyMap;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@
|
|||
package org.apache.cassandra.index.sai.iterators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
|
|
@ -28,90 +26,97 @@ import org.apache.cassandra.index.sai.utils.PrimaryKey;
|
|||
import org.apache.cassandra.io.util.FileUtils;
|
||||
|
||||
/**
|
||||
* {@link KeyRangeConcatIterator} takes a list of sorted range iterator and concatenates them, leaving duplicates in
|
||||
* {@link KeyRangeConcatIterator} takes a list of sorted range iterators and concatenates them, leaving duplicates in
|
||||
* place, to produce a new stably sorted iterator. Duplicates are eliminated later in
|
||||
* {@link org.apache.cassandra.index.sai.plan.StorageAttachedIndexSearcher}
|
||||
* as results from multiple SSTable indexes and their respective segments are consumed.
|
||||
*
|
||||
* <p>
|
||||
* ex. (1, 2, 3) + (3, 3, 4, 5) -> (1, 2, 3, 3, 3, 4, 5)
|
||||
* ex. (1, 2, 2, 3) + (3, 4, 4, 6, 6, 7) -> (1, 2, 2, 3, 3, 4, 4, 6, 6, 7)
|
||||
*
|
||||
* TODO Investigate removing the use of PriorityQueue from this class <a href="https://issues.apache.org/jira/browse/CASSANDRA-18165">CASSANDRA-18165</a>
|
||||
*/
|
||||
public class KeyRangeConcatIterator extends KeyRangeIterator
|
||||
{
|
||||
public static final String MUST_BE_SORTED_ERROR = "RangeIterator must be sorted, previous max: %s, next min: %s";
|
||||
private final PriorityQueue<KeyRangeIterator> ranges;
|
||||
private final List<KeyRangeIterator> toRelease;
|
||||
private final List<KeyRangeIterator> ranges;
|
||||
|
||||
protected KeyRangeConcatIterator(KeyRangeIterator.Builder.Statistics statistics, PriorityQueue<KeyRangeIterator> ranges)
|
||||
private int current;
|
||||
|
||||
protected KeyRangeConcatIterator(KeyRangeIterator.Builder.Statistics statistics, List<KeyRangeIterator> ranges, Runnable onClose)
|
||||
{
|
||||
super(statistics);
|
||||
super(statistics, onClose);
|
||||
|
||||
if (ranges.isEmpty())
|
||||
throw new IllegalArgumentException("Cannot concatenate empty list of ranges");
|
||||
|
||||
this.current = 0;
|
||||
this.ranges = ranges;
|
||||
this.toRelease = new ArrayList<>(ranges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performSkipTo(PrimaryKey nextKey)
|
||||
{
|
||||
while (!ranges.isEmpty())
|
||||
while (current < ranges.size())
|
||||
{
|
||||
if (ranges.peek().getCurrent().compareTo(nextKey) >= 0)
|
||||
KeyRangeIterator currentIterator = ranges.get(current);
|
||||
|
||||
if (currentIterator.hasNext() && currentIterator.peek().compareTo(nextKey) >= 0)
|
||||
break;
|
||||
|
||||
KeyRangeIterator head = ranges.poll();
|
||||
|
||||
if (head.getMaximum().compareTo(nextKey) >= 0)
|
||||
if (currentIterator.getMaximum().compareTo(nextKey) >= 0)
|
||||
{
|
||||
head.skipTo(nextKey);
|
||||
ranges.add(head);
|
||||
currentIterator.skipTo(nextKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
// due to lazy key fetching, we cannot close iterator immediately
|
||||
FileUtils.closeQuietly(toRelease);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrimaryKey computeNext()
|
||||
{
|
||||
while (!ranges.isEmpty())
|
||||
while (current < ranges.size())
|
||||
{
|
||||
KeyRangeIterator current = ranges.poll();
|
||||
if (current.hasNext())
|
||||
{
|
||||
PrimaryKey next = current.next();
|
||||
// hasNext will update RangeIterator's current which is used to sort in PQ
|
||||
if (current.hasNext())
|
||||
ranges.add(current);
|
||||
KeyRangeIterator currentIterator = ranges.get(current);
|
||||
|
||||
return next;
|
||||
}
|
||||
if (currentIterator.hasNext())
|
||||
return currentIterator.next();
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
return endOfData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
super.close();
|
||||
|
||||
// due to lazy key fetching, we cannot close iterator immediately
|
||||
FileUtils.closeQuietly(ranges);
|
||||
}
|
||||
|
||||
public static Builder builder(int size)
|
||||
{
|
||||
return new Builder(size);
|
||||
return builder(size, () -> {});
|
||||
}
|
||||
|
||||
public static Builder builder(int size, Runnable onClose)
|
||||
{
|
||||
return new Builder(size, onClose);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static class Builder extends KeyRangeIterator.Builder
|
||||
{
|
||||
private final PriorityQueue<KeyRangeIterator> ranges;
|
||||
// We can use a list because the iterators are already in order
|
||||
private final List<KeyRangeIterator> ranges;
|
||||
|
||||
Builder(int size)
|
||||
Builder(int size, Runnable onClose)
|
||||
{
|
||||
super(new ConcatStatistics());
|
||||
ranges = new PriorityQueue<>(size, Comparator.comparing(KeyRangeIterator::getCurrent));
|
||||
super(new ConcatStatistics(), onClose);
|
||||
this.ranges = new ArrayList<>(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -120,7 +125,7 @@ public class KeyRangeConcatIterator extends KeyRangeIterator
|
|||
if (range == null)
|
||||
return this;
|
||||
|
||||
if (range.getCount() > 0)
|
||||
if (range.getMaxKeys() > 0)
|
||||
ranges.add(range);
|
||||
else
|
||||
FileUtils.closeQuietly(range);
|
||||
|
|
@ -138,16 +143,26 @@ public class KeyRangeConcatIterator extends KeyRangeIterator
|
|||
@Override
|
||||
public void cleanup()
|
||||
{
|
||||
super.cleanup();
|
||||
FileUtils.closeQuietly(ranges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyRangeIterator buildIterator()
|
||||
{
|
||||
if (rangeCount() == 0)
|
||||
{
|
||||
onClose.run();
|
||||
return empty();
|
||||
}
|
||||
if (rangeCount() == 1)
|
||||
return ranges.poll();
|
||||
{
|
||||
KeyRangeIterator single = ranges.get(0);
|
||||
single.setOnClose(onClose);
|
||||
return single;
|
||||
}
|
||||
|
||||
return new KeyRangeConcatIterator(statistics, ranges);
|
||||
return new KeyRangeConcatIterator(statistics, ranges, onClose);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +172,7 @@ public class KeyRangeConcatIterator extends KeyRangeIterator
|
|||
public void update(KeyRangeIterator range)
|
||||
{
|
||||
// range iterators should be sorted, but previous max must not be greater than next min.
|
||||
if (range.getCount() > 0)
|
||||
if (range.getMaxKeys() > 0)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
|
|
@ -169,7 +184,7 @@ public class KeyRangeConcatIterator extends KeyRangeIterator
|
|||
}
|
||||
|
||||
max = range.getMaximum();
|
||||
count += range.getCount();
|
||||
count += range.getMaxKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import javax.annotation.Nullable;
|
|||
* A simple intersection iterator that makes no real attempts at optimising the iteration apart from
|
||||
* initially sorting the ranges. This implementation also supports an intersection limit via
|
||||
* {@code CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT} which limits the number of ranges that will
|
||||
* be included in the intersection.
|
||||
* be included in the intersection. This currently defaults to 2.
|
||||
* <p>
|
||||
* Intersection only works for ranges that are compatible according to {@link PrimaryKey.Kind#isIntersectable(Kind)}.
|
||||
*/
|
||||
|
|
@ -52,21 +52,20 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
}
|
||||
|
||||
private final List<KeyRangeIterator> ranges;
|
||||
private PrimaryKey highestKey;
|
||||
|
||||
private KeyRangeIntersectionIterator(Builder.Statistics statistics, List<KeyRangeIterator> ranges)
|
||||
private KeyRangeIntersectionIterator(Builder.Statistics statistics, List<KeyRangeIterator> ranges, Runnable onClose)
|
||||
{
|
||||
super(statistics);
|
||||
super(statistics, onClose);
|
||||
this.ranges = ranges;
|
||||
this.highestKey = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrimaryKey computeNext()
|
||||
{
|
||||
// Advance one iterator to the next key and remember the key as the highest seen so far.
|
||||
// It can become null when we reach the end of the iterator.
|
||||
// If there are both static and non-static keys being iterated here, we advance a non-static one,
|
||||
// regardless of the order of ranges in the ranges list.
|
||||
PrimaryKey highestKey = advanceOneRange();
|
||||
if (highestKey == null)
|
||||
highestKey = computeHighestKey();
|
||||
|
||||
outer:
|
||||
// After advancing one iterator, we must try to advance all the other iterators that got behind,
|
||||
|
|
@ -81,14 +80,17 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
// Once this inner loop finishes normally, all iterators are guaranteed to be at the same value.
|
||||
for (KeyRangeIterator range : ranges)
|
||||
{
|
||||
if (range.getCurrent().compareTo(highestKey) < 0)
|
||||
if (!range.hasNext())
|
||||
return endOfData();
|
||||
|
||||
if (range.peek().compareTo(highestKey) < 0)
|
||||
{
|
||||
// If we advance a STATIC key, then we must advance it to the same partition as the highestKey.
|
||||
// Advancing a STATIC key to a WIDE key directly (without throwing away the clustering) would
|
||||
// go too far, as WIDE keys are stored after STATIC in the posting list.
|
||||
PrimaryKey nextKey = range.getCurrent().kind() == Kind.STATIC
|
||||
? nextOrNull(range, highestKey.toStatic())
|
||||
: nextOrNull(range, highestKey);
|
||||
PrimaryKey nextKey = range.peek().kind() == Kind.STATIC
|
||||
? skipAndPeek(range, highestKey.toStatic())
|
||||
: skipAndPeek(range, highestKey);
|
||||
|
||||
// We use strict comparison here, since it orders WIDE primary keys after STATIC primary keys
|
||||
// in the same partition. When WIDE keys are present, we want to return them rather than STATIC
|
||||
|
|
@ -111,7 +113,17 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
|
||||
// If we get here, all iterators have been advanced to the same key. When STATIC and WIDE keys are
|
||||
// mixed, this means WIDE keys point to exactly the same row, and STATIC keys the same partition.
|
||||
return highestKey;
|
||||
PrimaryKey result = highestKey;
|
||||
|
||||
// Advance one iterator to the next key and remember the key as the highest seen so far.
|
||||
// It can become null when we reach the end of the iterator.
|
||||
// If there are both static and non-static keys being iterated here, we advance a non-static one,
|
||||
// regardless of the order of ranges in the ranges list.
|
||||
highestKey = advanceOneRange();
|
||||
|
||||
// If we get here, all iterators have been advanced to the same key. When STATIC and WIDE keys are
|
||||
// mixed, this means WIDE keys point to exactly the same row, and STATIC keys the same partition.
|
||||
return result;
|
||||
}
|
||||
|
||||
return endOfData();
|
||||
|
|
@ -125,30 +137,53 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
*/
|
||||
private @Nullable PrimaryKey advanceOneRange()
|
||||
{
|
||||
PrimaryKey highestKey = getCurrent();
|
||||
for (KeyRangeIterator range : ranges)
|
||||
if (range.peek().kind() != Kind.STATIC)
|
||||
{
|
||||
range.next();
|
||||
return range.hasNext() ? range.peek() : null;
|
||||
}
|
||||
|
||||
for (KeyRangeIterator range : ranges)
|
||||
if (range.getCurrent().kind() != Kind.STATIC)
|
||||
return nextOrNull(range, highestKey);
|
||||
|
||||
for (KeyRangeIterator range : ranges)
|
||||
if (range.getCurrent().kind() == Kind.STATIC)
|
||||
return nextOrNull(range, highestKey);
|
||||
if (range.peek().kind() == Kind.STATIC)
|
||||
{
|
||||
range.next();
|
||||
return range.hasNext() ? range.peek() : null;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("There should be at least one range to advance!");
|
||||
}
|
||||
|
||||
private @Nullable PrimaryKey computeHighestKey()
|
||||
{
|
||||
PrimaryKey max = getMinimum();
|
||||
for (KeyRangeIterator range : ranges)
|
||||
{
|
||||
if (!range.hasNext())
|
||||
return null;
|
||||
if (range.peek().compareToStrict(max) > 0)
|
||||
max = range.peek();
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performSkipTo(PrimaryKey nextKey)
|
||||
{
|
||||
// Resist the temptation to call range.hasNext before skipTo: this is a pessimisation, hasNext will invoke
|
||||
// computeNext under the hood, which is an expensive operation to produce a value that we plan to throw away.
|
||||
// Instead, it is the responsibility of the child iterators to make skipTo fast when the iterator is exhausted.
|
||||
for (KeyRangeIterator range : ranges)
|
||||
if (range.hasNext())
|
||||
range.skipTo(nextKey);
|
||||
range.skipTo(nextKey);
|
||||
|
||||
// Force recomputing the highest key on the next call to computeNext()
|
||||
highestKey = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
super.close();
|
||||
FileUtils.closeQuietly(ranges);
|
||||
}
|
||||
|
||||
|
|
@ -156,21 +191,26 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
* Fetches the next available item from the iterator, such that the item is not lower than the given key.
|
||||
* If no such items are available, returns null.
|
||||
*/
|
||||
private PrimaryKey nextOrNull(KeyRangeIterator iterator, PrimaryKey minKey)
|
||||
private PrimaryKey skipAndPeek(KeyRangeIterator iterator, PrimaryKey minKey)
|
||||
{
|
||||
iterator.skipTo(minKey);
|
||||
return iterator.hasNext() ? iterator.next() : null;
|
||||
return iterator.hasNext() ? iterator.peek() : null;
|
||||
}
|
||||
|
||||
public static Builder builder(int size)
|
||||
public static Builder builder(int size, int limit)
|
||||
{
|
||||
return new Builder(size);
|
||||
return builder(size, limit, () -> {});
|
||||
}
|
||||
|
||||
public static Builder builder(int size, Runnable onClose)
|
||||
{
|
||||
return new Builder(size, onClose);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Builder builder(int size, int limit)
|
||||
public static Builder builder(int size, int limit, Runnable onClose)
|
||||
{
|
||||
return new Builder(size, limit);
|
||||
return new Builder(size, limit, onClose);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -187,14 +227,14 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
|
||||
protected final List<KeyRangeIterator> rangeIterators;
|
||||
|
||||
Builder(int size)
|
||||
Builder(int size, Runnable onClose)
|
||||
{
|
||||
this(size, CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.getInt());
|
||||
this(size, CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.getInt(), onClose);
|
||||
}
|
||||
|
||||
Builder(int size, int limit)
|
||||
Builder(int size, int limit, Runnable onClose)
|
||||
{
|
||||
super(new IntersectionStatistics());
|
||||
super(new IntersectionStatistics(), onClose);
|
||||
rangeIterators = new ArrayList<>(size);
|
||||
this.limit = limit;
|
||||
}
|
||||
|
|
@ -205,7 +245,7 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
if (range == null)
|
||||
return this;
|
||||
|
||||
if (range.getCount() > 0)
|
||||
if (range.getMaxKeys() > 0)
|
||||
rangeIterators.add(range);
|
||||
else
|
||||
FileUtils.closeQuietly(range);
|
||||
|
|
@ -224,13 +264,14 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
@Override
|
||||
public void cleanup()
|
||||
{
|
||||
super.cleanup();
|
||||
FileUtils.closeQuietly(rangeIterators);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyRangeIterator buildIterator()
|
||||
{
|
||||
rangeIterators.sort(Comparator.comparingLong(KeyRangeIterator::getCount));
|
||||
rangeIterators.sort(Comparator.comparingLong(KeyRangeIterator::getMaxKeys));
|
||||
int initialSize = rangeIterators.size();
|
||||
// all ranges will be included
|
||||
if (limit >= rangeIterators.size() || limit <= 0)
|
||||
|
|
@ -248,7 +289,7 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
Tracing.trace("Selecting {} {} of {} out of {} indexes",
|
||||
rangeIterators.size(),
|
||||
rangeIterators.size() > 1 ? "indexes with cardinalities" : "index with cardinality",
|
||||
rangeIterators.stream().map(KeyRangeIterator::getCount).map(Object::toString).collect(Collectors.joining(", ")),
|
||||
rangeIterators.stream().map(KeyRangeIterator::getMaxKeys).map(Object::toString).collect(Collectors.joining(", ")),
|
||||
initialSize);
|
||||
|
||||
return buildIterator(selectiveStatistics, rangeIterators);
|
||||
|
|
@ -266,18 +307,27 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
if (isDisjoint)
|
||||
{
|
||||
FileUtils.closeQuietly(ranges);
|
||||
onClose.run();
|
||||
return KeyRangeIterator.empty();
|
||||
}
|
||||
|
||||
if (ranges.size() == 1)
|
||||
return ranges.get(0);
|
||||
{
|
||||
KeyRangeIterator single = ranges.get(0);
|
||||
single.setOnClose(onClose);
|
||||
return single;
|
||||
}
|
||||
|
||||
// Make sure intersection is supported on the ranges provided:
|
||||
PrimaryKey.Kind firstKind = null;
|
||||
|
||||
for (KeyRangeIterator range : ranges)
|
||||
{
|
||||
PrimaryKey key = range.getCurrent();
|
||||
PrimaryKey key;
|
||||
if(range.hasNext())
|
||||
key = range.peek();
|
||||
else
|
||||
key = range.getMaximum();
|
||||
|
||||
if (key != null)
|
||||
if (firstKind == null)
|
||||
|
|
@ -286,7 +336,7 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
throw new IllegalArgumentException("Cannot intersect " + firstKind + " and " + key.kind() + " ranges!");
|
||||
}
|
||||
|
||||
return new KeyRangeIntersectionIterator(statistics, ranges);
|
||||
return new KeyRangeIntersectionIterator(statistics, ranges, onClose);
|
||||
}
|
||||
|
||||
private void updateStatistics(Statistics statistics, KeyRangeIterator range)
|
||||
|
|
@ -310,11 +360,11 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
if (empty)
|
||||
{
|
||||
empty = false;
|
||||
count = range.getCount();
|
||||
count = range.getMaxKeys();
|
||||
}
|
||||
else
|
||||
{
|
||||
count = Math.min(count, range.getCount());
|
||||
count = Math.min(count, range.getMaxKeys());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,7 +372,7 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
@VisibleForTesting
|
||||
protected static boolean isDisjoint(KeyRangeIterator a, KeyRangeIterator b)
|
||||
{
|
||||
return isDisjointInternal(a.getCurrent(), a.getMaximum(), b);
|
||||
return isDisjointInternal(a.peek(), a.getMaximum(), b);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -344,6 +394,6 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator
|
|||
*/
|
||||
private static boolean isDisjointInternal(PrimaryKey min, PrimaryKey max, KeyRangeIterator b)
|
||||
{
|
||||
return min == null || max == null || b.getCount() == 0 || min.compareTo(b.getMaximum()) > 0 || b.getCurrent().compareTo(max) > 0;
|
||||
return min == null || max == null || b.getMaxKeys() == 0 || min.compareTo(b.getMaximum()) > 0 || (b.hasNext() && b.peek().compareTo(max) > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,40 +26,55 @@ import com.google.common.collect.Iterables;
|
|||
import org.apache.cassandra.index.sai.utils.PrimaryKey;
|
||||
import org.apache.cassandra.utils.AbstractGuavaIterator;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* An abstract implementation of {@link AbstractGuavaIterator} that supports the building and management of
|
||||
* concatanation, union and intersection iterators.
|
||||
*
|
||||
* <p>
|
||||
* Range iterators contain primary keys, in sorted order, with no duplicates. They also
|
||||
* know their minimum and maximum keys, and an upper bound on the number of keys they contain.
|
||||
* <p>
|
||||
* Only certain methods are designed to be overriden. The others are marked private or final.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey> implements Closeable
|
||||
{
|
||||
private final PrimaryKey min, max;
|
||||
private final long count;
|
||||
private PrimaryKey current;
|
||||
private Runnable onClose;
|
||||
|
||||
protected KeyRangeIterator(Builder.Statistics statistics)
|
||||
protected KeyRangeIterator(Builder.Statistics statistics, Runnable onClose)
|
||||
{
|
||||
this(statistics.min, statistics.max, statistics.count);
|
||||
this(statistics.min, statistics.max, statistics.count, onClose);
|
||||
}
|
||||
|
||||
public KeyRangeIterator(KeyRangeIterator range)
|
||||
public KeyRangeIterator(KeyRangeIterator range, Runnable onClose)
|
||||
{
|
||||
this(range == null ? null : range.min,
|
||||
range == null ? null : range.max,
|
||||
range == null ? -1 : range.count);
|
||||
range == null ? -1 : range.count,
|
||||
onClose);
|
||||
}
|
||||
|
||||
public KeyRangeIterator(PrimaryKey min, PrimaryKey max, long count)
|
||||
{
|
||||
this(min, max, count, () -> {});
|
||||
}
|
||||
|
||||
public KeyRangeIterator(PrimaryKey min, PrimaryKey max, long count, Runnable onClose)
|
||||
{
|
||||
boolean isComplete = min != null && max != null && count != 0;
|
||||
boolean isEmpty = min == null && max == null && (count == 0 || count == -1);
|
||||
Preconditions.checkArgument(isComplete || isEmpty, "Range: [%s,%s], Count: %d", min, max, count);
|
||||
|
||||
if (isEmpty)
|
||||
endOfData();
|
||||
|
||||
this.min = min;
|
||||
this.current = min;
|
||||
this.max = max;
|
||||
this.count = count;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
public final PrimaryKey getMinimum()
|
||||
|
|
@ -67,75 +82,63 @@ public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey>
|
|||
return min;
|
||||
}
|
||||
|
||||
public final PrimaryKey getCurrent()
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
public final PrimaryKey getMaximum()
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
public final long getCount()
|
||||
/**
|
||||
* @return an upper bound on the number of keys that can be returned by this iterator.
|
||||
*/
|
||||
public final long getMaxKeys()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called, this iterators current position should
|
||||
* When called, this iterator's current position will
|
||||
* be skipped forwards until finding either:
|
||||
* 1) an element equal to or bigger than next
|
||||
* 1) an element equal to or bigger than nextKey
|
||||
* 2) the end of the iterator
|
||||
*
|
||||
* @param nextKey value to skip the iterator forward until matching
|
||||
*
|
||||
* @return The key skipped to, which will be the key returned by the
|
||||
* next call to {@link #next()}, i.e., we are "peeking" at the next key as part of the skip.
|
||||
*/
|
||||
public final PrimaryKey skipTo(PrimaryKey nextKey)
|
||||
public final void skipTo(PrimaryKey nextKey)
|
||||
{
|
||||
if (min == null || max == null)
|
||||
return endOfData();
|
||||
if (state == State.DONE)
|
||||
return;
|
||||
|
||||
// In the case of deferred iterators the current value may not accurately
|
||||
// reflect the next value, so we need to check that as well
|
||||
if (current.compareTo(nextKey) >= 0)
|
||||
{
|
||||
next = next == null ? recomputeNext() : next;
|
||||
if (next == null)
|
||||
return endOfData();
|
||||
else if (next.compareTo(nextKey) >= 0)
|
||||
return next;
|
||||
}
|
||||
if (state == State.READY && next.compareTo(nextKey) >= 0)
|
||||
return;
|
||||
|
||||
if (max.compareTo(nextKey) < 0)
|
||||
return endOfData();
|
||||
{
|
||||
endOfData();
|
||||
return;
|
||||
}
|
||||
|
||||
performSkipTo(nextKey);
|
||||
return recomputeNext();
|
||||
state = State.NOT_READY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip to nextKey.
|
||||
*
|
||||
* <p>
|
||||
* That is, implementations should set up the iterator state such that
|
||||
* calling computeNext() will return nextKey if present,
|
||||
* or the first one after it if not present.
|
||||
*/
|
||||
protected abstract void performSkipTo(PrimaryKey nextKey);
|
||||
|
||||
private PrimaryKey recomputeNext()
|
||||
public void setOnClose(Runnable onClose)
|
||||
{
|
||||
return tryToComputeNext() ? peek() : endOfData();
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final boolean tryToComputeNext()
|
||||
public void close()
|
||||
{
|
||||
boolean hasNext = super.tryToComputeNext();
|
||||
current = hasNext ? next : getMaximum();
|
||||
return hasNext;
|
||||
onClose.run();
|
||||
}
|
||||
|
||||
public static KeyRangeIterator empty()
|
||||
|
|
@ -146,7 +149,7 @@ public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey>
|
|||
private static class EmptyRangeIterator extends KeyRangeIterator
|
||||
{
|
||||
static final KeyRangeIterator instance = new EmptyRangeIterator();
|
||||
EmptyRangeIterator() { super(null, null, 0); }
|
||||
EmptyRangeIterator() { super(null, null, 0, () -> {}); }
|
||||
public PrimaryKey computeNext() { return endOfData(); }
|
||||
protected void performSkipTo(PrimaryKey nextKey) { }
|
||||
public void close() { }
|
||||
|
|
@ -156,10 +159,12 @@ public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey>
|
|||
public static abstract class Builder
|
||||
{
|
||||
protected final Statistics statistics;
|
||||
protected final Runnable onClose;
|
||||
|
||||
public Builder(Statistics statistics)
|
||||
public Builder(Statistics statistics, Runnable onClose)
|
||||
{
|
||||
this.statistics = statistics;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
public PrimaryKey getMinimum()
|
||||
|
|
@ -189,16 +194,24 @@ public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey>
|
|||
public final KeyRangeIterator build()
|
||||
{
|
||||
if (rangeCount() == 0)
|
||||
{
|
||||
onClose.run();
|
||||
return empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
return buildIterator();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Builder add(KeyRangeIterator range);
|
||||
|
||||
public abstract int rangeCount();
|
||||
|
||||
public abstract void cleanup();
|
||||
public void cleanup()
|
||||
{
|
||||
onClose.run();
|
||||
}
|
||||
|
||||
protected abstract KeyRangeIterator buildIterator();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class KeyRangeOrderingIterator extends KeyRangeIterator
|
|||
|
||||
public KeyRangeOrderingIterator(KeyRangeIterator input, int chunkSize, Function<List<PrimaryKey>, KeyRangeIterator> nextRangeFunction)
|
||||
{
|
||||
super(input);
|
||||
super(input, () -> {});
|
||||
this.input = input;
|
||||
this.chunkSize = chunkSize;
|
||||
this.nextRangeFunction = nextRangeFunction;
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
private final List<KeyRangeIterator> ranges;
|
||||
private final List<KeyRangeIterator> candidates;
|
||||
|
||||
private KeyRangeUnionIterator(Builder.Statistics statistics, List<KeyRangeIterator> ranges)
|
||||
private KeyRangeUnionIterator(Builder.Statistics statistics, List<KeyRangeIterator> ranges, Runnable onClose)
|
||||
{
|
||||
super(statistics);
|
||||
super(statistics, onClose);
|
||||
this.ranges = ranges;
|
||||
this.candidates = new ArrayList<>(ranges.size());
|
||||
}
|
||||
|
|
@ -43,38 +43,42 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
@Override
|
||||
public PrimaryKey computeNext()
|
||||
{
|
||||
// the design is to find the next best value from all the ranges,
|
||||
// and then advance all the ranges that have the same value.
|
||||
candidates.clear();
|
||||
PrimaryKey candidateKey = null;
|
||||
for (KeyRangeIterator range : ranges)
|
||||
{
|
||||
if (range.hasNext())
|
||||
if (!range.hasNext())
|
||||
continue;
|
||||
|
||||
if (candidateKey == null)
|
||||
{
|
||||
if (candidateKey == null)
|
||||
candidateKey = range.peek();
|
||||
candidates.add(range);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrimaryKey peeked = range.peek();
|
||||
|
||||
int cmp = candidateKey.compareTo(peeked);
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
candidateKey = range.peek();
|
||||
// Replace any existing candidate key if this one is STATIC:
|
||||
if (peeked.kind() == PrimaryKey.Kind.STATIC)
|
||||
candidateKey = peeked;
|
||||
|
||||
candidates.add(range);
|
||||
}
|
||||
else
|
||||
else if (cmp > 0)
|
||||
{
|
||||
PrimaryKey peeked = range.peek();
|
||||
|
||||
int cmp = candidateKey.compareTo(peeked);
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
// Replace any existing candidate key if this one is STATIC:
|
||||
if (peeked.kind() == PrimaryKey.Kind.STATIC)
|
||||
candidateKey = peeked;
|
||||
|
||||
candidates.add(range);
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
candidates.clear();
|
||||
candidateKey = peeked;
|
||||
candidates.add(range);
|
||||
}
|
||||
// we found a new best candidate, throw away the old ones
|
||||
candidates.clear();
|
||||
candidateKey = peeked;
|
||||
candidates.add(range);
|
||||
}
|
||||
// else, existing candidate is less than the next in this range
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty())
|
||||
|
|
@ -106,18 +110,29 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
super.close();
|
||||
|
||||
// Due to lazy key fetching, we cannot close iterator immediately
|
||||
FileUtils.closeQuietly(ranges);
|
||||
}
|
||||
|
||||
public static Builder builder(int size)
|
||||
{
|
||||
return new Builder(size);
|
||||
return builder(size, () -> {});
|
||||
}
|
||||
|
||||
public static Builder builder(int size, Runnable onClose)
|
||||
{
|
||||
return new Builder(size, onClose);
|
||||
}
|
||||
|
||||
public static KeyRangeIterator build(List<KeyRangeIterator> keys, Runnable onClose)
|
||||
{
|
||||
return new Builder(keys.size(), onClose).add(keys).build();
|
||||
}
|
||||
public static KeyRangeIterator build(List<KeyRangeIterator> keys)
|
||||
{
|
||||
return new Builder(keys.size()).add(keys).build();
|
||||
return build(keys, () -> {});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -125,9 +140,9 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
{
|
||||
protected final List<KeyRangeIterator> rangeIterators;
|
||||
|
||||
Builder(int size)
|
||||
Builder(int size, Runnable onClose)
|
||||
{
|
||||
super(new UnionStatistics());
|
||||
super(new UnionStatistics(), onClose);
|
||||
this.rangeIterators = new ArrayList<>(size);
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +152,7 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
if (range == null)
|
||||
return this;
|
||||
|
||||
if (range.getCount() > 0)
|
||||
if (range.getMaxKeys() > 0)
|
||||
{
|
||||
rangeIterators.add(range);
|
||||
statistics.update(range);
|
||||
|
|
@ -159,6 +174,7 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
@Override
|
||||
public void cleanup()
|
||||
{
|
||||
super.cleanup();
|
||||
FileUtils.closeQuietly(rangeIterators);
|
||||
}
|
||||
|
||||
|
|
@ -166,9 +182,13 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
protected KeyRangeIterator buildIterator()
|
||||
{
|
||||
if (rangeCount() == 1)
|
||||
return rangeIterators.get(0);
|
||||
{
|
||||
KeyRangeIterator single = rangeIterators.get(0);
|
||||
single.setOnClose(onClose);
|
||||
return single;
|
||||
}
|
||||
|
||||
return new KeyRangeUnionIterator(statistics, rangeIterators);
|
||||
return new KeyRangeUnionIterator(statistics, rangeIterators, onClose);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +199,7 @@ public class KeyRangeUnionIterator extends KeyRangeIterator
|
|||
{
|
||||
min = nullSafeMin(min, range.getMinimum());
|
||||
max = nullSafeMax(max, range.getMaximum());
|
||||
count += range.getCount();
|
||||
count += range.getMaxKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class InMemoryKeyRangeIterator extends KeyRangeIterator
|
|||
*/
|
||||
public InMemoryKeyRangeIterator(SortedSet<PrimaryKey> keys)
|
||||
{
|
||||
super(keys.first(), keys.last(), keys.size());
|
||||
super(keys.first(), keys.last(), keys.size(), () -> {});
|
||||
this.keys = new PriorityQueue<>(keys);
|
||||
this.uniqueKeys = true;
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public class InMemoryKeyRangeIterator extends KeyRangeIterator
|
|||
*/
|
||||
public InMemoryKeyRangeIterator(PrimaryKey min, PrimaryKey max, PriorityQueue<PrimaryKey> keys)
|
||||
{
|
||||
super(min, max, keys.size());
|
||||
super(min, max, keys.size(), () -> {});
|
||||
this.keys = keys;
|
||||
this.uniqueKeys = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ public class QueryController
|
|||
* the {@link SSTableIndex}s that will satisfy the expression.
|
||||
* <p>
|
||||
* Each (expression, SSTable indexes) pair is then passed to
|
||||
* {@link IndexSearchResultIterator#build(Expression, Collection, AbstractBounds, QueryContext, boolean)}
|
||||
* {@link IndexSearchResultIterator#build(Expression, Collection, AbstractBounds, QueryContext, boolean, Runnable)}
|
||||
* to search the in-memory index associated with the expression and the SSTable indexes, the results of
|
||||
* which are unioned and returned.
|
||||
* <p>
|
||||
* The results from each call to {@link IndexSearchResultIterator#build(Expression, Collection, AbstractBounds, QueryContext, boolean)}
|
||||
* The results from each call to {@link IndexSearchResultIterator#build(Expression, Collection, AbstractBounds, QueryContext, boolean, Runnable)}
|
||||
* are added to a {@link KeyRangeIntersectionIterator} and returned if strict filtering is allowed.
|
||||
* <p>
|
||||
* If strict filtering is not allowed, indexes are split into two groups according to the repaired status of their
|
||||
|
|
@ -195,10 +195,11 @@ public class QueryController
|
|||
// VSTODO move ANN out of expressions and into its own abstraction? That will help get generic ORDER BY support
|
||||
expressions = expressions.stream().filter(e -> e.getIndexOperator() != Expression.IndexOperator.ANN).collect(Collectors.toList());
|
||||
|
||||
KeyRangeIterator.Builder builder = command.rowFilter().isStrict()
|
||||
? KeyRangeIntersectionIterator.builder(expressions.size())
|
||||
: KeyRangeUnionIterator.builder(expressions.size());
|
||||
QueryViewBuilder.QueryView queryView = new QueryViewBuilder(expressions, mergeRange).build();
|
||||
Runnable onClose = () -> queryView.referencedIndexes.forEach(SSTableIndex::releaseQuietly);
|
||||
KeyRangeIterator.Builder builder = command.rowFilter().isStrict()
|
||||
? KeyRangeIntersectionIterator.builder(expressions.size(), onClose)
|
||||
: KeyRangeUnionIterator.builder(expressions.size(), onClose);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -210,11 +211,11 @@ public class QueryController
|
|||
// This usually means we are making this local index query in the context of a user query that reads
|
||||
// from a single replica and thus can safely perform local intersections.
|
||||
for (Pair<Expression, Collection<SSTableIndex>> queryViewPair : queryView.view)
|
||||
builder.add(IndexSearchResultIterator.build(queryViewPair.left, queryViewPair.right, mergeRange, queryContext, true));
|
||||
builder.add(IndexSearchResultIterator.build(queryViewPair.left, queryViewPair.right, mergeRange, queryContext, true, () -> {}));
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyRangeIterator.Builder repairedBuilder = KeyRangeIntersectionIterator.builder(expressions.size());
|
||||
KeyRangeIterator.Builder repairedBuilder = KeyRangeIntersectionIterator.builder(expressions.size(), () -> {});
|
||||
|
||||
for (Pair<Expression, Collection<SSTableIndex>> queryViewPair : queryView.view)
|
||||
{
|
||||
|
|
@ -232,10 +233,10 @@ public class QueryController
|
|||
|
||||
// Always build an iterator for the un-repaired set, given this must include Memtable indexes...
|
||||
IndexSearchResultIterator unrepairedIterator =
|
||||
IndexSearchResultIterator.build(queryViewPair.left, unrepaired, mergeRange, queryContext, true);
|
||||
IndexSearchResultIterator.build(queryViewPair.left, unrepaired, mergeRange, queryContext, true, () -> {});
|
||||
|
||||
// ...but ignore it if our combined results are empty.
|
||||
if (unrepairedIterator.getCount() > 0)
|
||||
if (unrepairedIterator.getMaxKeys() > 0)
|
||||
{
|
||||
builder.add(unrepairedIterator);
|
||||
queryContext.hasUnrepairedMatches = true;
|
||||
|
|
@ -248,7 +249,7 @@ public class QueryController
|
|||
|
||||
// ...then only add an iterator to the repaired intersection if repaired SSTable indexes exist.
|
||||
if (!repaired.isEmpty())
|
||||
repairedBuilder.add(IndexSearchResultIterator.build(queryViewPair.left, repaired, mergeRange, queryContext, false));
|
||||
repairedBuilder.add(IndexSearchResultIterator.build(queryViewPair.left, repaired, mergeRange, queryContext, false, () -> {}));
|
||||
}
|
||||
|
||||
if (repairedBuilder.rangeCount() > 0)
|
||||
|
|
@ -259,7 +260,6 @@ public class QueryController
|
|||
{
|
||||
// all sstable indexes in view have been referenced, need to clean up when exception is thrown
|
||||
builder.cleanup();
|
||||
queryView.referencedIndexes.forEach(SSTableIndex::releaseQuietly);
|
||||
throw t;
|
||||
}
|
||||
return builder;
|
||||
|
|
@ -315,6 +315,7 @@ public class QueryController
|
|||
KeyRangeIterator memtableResults = index.memtableIndexManager().searchMemtableIndexes(queryContext, planExpression, mergeRange);
|
||||
|
||||
QueryViewBuilder.QueryView queryView = new QueryViewBuilder(Collections.singleton(planExpression), mergeRange).build();
|
||||
Runnable onClose = () -> queryView.referencedIndexes.forEach(SSTableIndex::releaseQuietly);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -322,12 +323,13 @@ public class QueryController
|
|||
.stream()
|
||||
.map(this::createRowIdIterator)
|
||||
.collect(Collectors.toList());
|
||||
return IndexSearchResultIterator.build(sstableIntersections, memtableResults, queryView.referencedIndexes, queryContext);
|
||||
|
||||
return IndexSearchResultIterator.build(sstableIntersections, memtableResults, queryView.referencedIndexes, queryContext, onClose);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
// all sstable indexes in view have been referenced, need to clean up when exception is thrown
|
||||
queryView.referencedIndexes.forEach(SSTableIndex::release);
|
||||
onClose.run();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
|
@ -353,6 +355,7 @@ public class QueryController
|
|||
// search memtable before referencing sstable indexes; otherwise we may miss newly flushed memtable index
|
||||
KeyRangeIterator memtableResults = index.memtableIndexManager().limitToTopResults(queryContext, sourceKeys, planExpression);
|
||||
QueryViewBuilder.QueryView queryView = new QueryViewBuilder(Collections.singleton(planExpression), mergeRange).build();
|
||||
Runnable onClose = () -> queryView.referencedIndexes.forEach(SSTableIndex::releaseQuietly);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -371,12 +374,12 @@ public class QueryController
|
|||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return IndexSearchResultIterator.build(sstableIntersections, memtableResults, queryView.referencedIndexes, queryContext);
|
||||
return IndexSearchResultIterator.build(sstableIntersections, memtableResults, queryView.referencedIndexes, queryContext, onClose);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
// all sstable indexes in view have been referenced, need to clean up when exception is thrown
|
||||
queryView.referencedIndexes.forEach(SSTableIndex::release);
|
||||
onClose.run();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import java.util.NoSuchElementException;
|
|||
|
||||
import com.google.common.collect.PeekingIterator;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
|
|
@ -31,9 +33,10 @@ import static com.google.common.base.Preconditions.checkState;
|
|||
* is that the next variable is now protected so that the KeyRangeIterator.skipTo
|
||||
* method can avoid early state changed.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public abstract class AbstractGuavaIterator<T> implements PeekingIterator<T>
|
||||
{
|
||||
private State state = State.NOT_READY;
|
||||
protected State state = State.NOT_READY;
|
||||
|
||||
/** Constructor for use by subclasses. */
|
||||
protected AbstractGuavaIterator() {}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||
|
||||
public class SingleNodeQueryFailureTest extends SAITester
|
||||
{
|
||||
private static final String CREATE_TABLE_TEMPLATE = "CREATE TABLE %s (id text PRIMARY KEY, v1 text) WITH " +
|
||||
private static final String CREATE_TABLE_TEMPLATE = "CREATE TABLE %s (id text PRIMARY KEY, v1 int, v2 text) WITH " +
|
||||
"compaction = {'class' : 'SizeTieredCompactionStrategy', 'enabled' : false }";
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
requireNetwork();
|
||||
setupTableAndIndexes();
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -52,47 +53,57 @@ public class SingleNodeQueryFailureTest extends SAITester
|
|||
Injections.deleteAll();
|
||||
}
|
||||
|
||||
// Single Index Tests
|
||||
@Test
|
||||
public void testFailedRangeIteratorOnSingleIndexQuery() throws Throwable
|
||||
{
|
||||
testFailedQuery("range_iterator_single", PostingListRangeIterator.class, "getNextRowId", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedTermsReaderOnSingleIndexQuery() throws Throwable
|
||||
{
|
||||
testFailedQuery("terms_reader_single", LiteralIndexSegmentTermsReader.TermQuery.class, "lookupPostingsOffset", true);
|
||||
}
|
||||
|
||||
// Multi Index Tests
|
||||
@Test
|
||||
public void testFailedRangeIteratorOnMultiIndexesQuery() throws Throwable
|
||||
{
|
||||
testFailedMultiIndexesQuery("range_iterator", PostingListRangeIterator.class, "getNextRowId");
|
||||
testFailedQuery("range_iterator_multi", PostingListRangeIterator.class, "getNextRowId", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedTermsReaderOnMultiIndexesQuery() throws Throwable
|
||||
{
|
||||
testFailedMultiIndexesQuery("terms_reader", LiteralIndexSegmentTermsReader.TermQuery.class, "lookupPostingsOffset");
|
||||
testFailedQuery("terms_reader_multi", LiteralIndexSegmentTermsReader.TermQuery.class, "lookupPostingsOffset", false);
|
||||
}
|
||||
|
||||
private void testFailedMultiIndexesQuery(String name, Class<?> targetClass, String targetMethod) throws Throwable
|
||||
private void setupTableAndIndexes()
|
||||
{
|
||||
Injection injection = Injections.newCustom(name)
|
||||
.add(newInvokePoint().onClass(targetClass).onMethod(targetMethod))
|
||||
.add(newActionBuilder().actions().doThrow(RuntimeException.class, quote("Injected failure!")))
|
||||
.build();
|
||||
|
||||
createTable(CREATE_TABLE_TEMPLATE);
|
||||
createIndex(String.format(CREATE_INDEX_TEMPLATE, "v1"));
|
||||
createIndex(String.format(CREATE_INDEX_TEMPLATE, "v2"));
|
||||
|
||||
execute("INSERT INTO %s (id, v1) VALUES ('1', '0')");
|
||||
execute("INSERT INTO %s (id, v1, v2) VALUES ('1', 0, '0')");
|
||||
flush();
|
||||
execute("INSERT INTO %s (id, v1) VALUES ('2', '1')");
|
||||
execute("INSERT INTO %s (id, v1, v2) VALUES ('2', 1, '1')");
|
||||
flush();
|
||||
execute("INSERT INTO %s (id, v1) VALUES ('3', '2')");
|
||||
execute("INSERT INTO %s (id, v1, v2) VALUES ('3', 2, '2')");
|
||||
flush();
|
||||
}
|
||||
|
||||
private void testFailedQuery(String name, Class<?> targetClass, String targetMethod, boolean isSingleIndexTest) throws Throwable
|
||||
{
|
||||
Injection injection = Injections.newCustom(name)
|
||||
.add(newInvokePoint().onClass(targetClass).onMethod(targetMethod))
|
||||
.add(newActionBuilder().actions().doThrow(RuntimeException.class, quote("Injected failure!")))
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
Injections.inject(injection);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 = '0'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 = '1'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 = '2'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
performTestQueries(isSingleIndexTest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -103,8 +114,49 @@ public class SingleNodeQueryFailureTest extends SAITester
|
|||
injection.disable();
|
||||
}
|
||||
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v1 = '0'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v1 = '1'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v1 = '2'").all().size());
|
||||
verifyResults(isSingleIndexTest);
|
||||
}
|
||||
|
||||
private void performTestQueries(boolean isSingleIndexTest)
|
||||
{
|
||||
if (isSingleIndexTest)
|
||||
{
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v2 = '0'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v2 = '1'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v2 = '2'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 < 1 and v2 = '0'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 >= 1 and v2 = '1'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
|
||||
assertThatThrownBy(() -> executeNet("SELECT id FROM %s WHERE v1 >= 2 and v2 = '2'"))
|
||||
.isInstanceOf(ReadFailureException.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyResults(boolean isSingleIndexTest)
|
||||
{
|
||||
if (isSingleIndexTest)
|
||||
{
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '0'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '1'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '2'").all().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.assertEquals(3, executeNet("SELECT id FROM %s WHERE v1 >= 0").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '0'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '1'").all().size());
|
||||
Assert.assertEquals(1, executeNet("SELECT id FROM %s WHERE v2 = '2'").all().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,6 @@ public class BalancedTreeIndexSearcherTest extends SAIRandomizedTester
|
|||
.add(Operator.EQ, rawType.decompose(rawValueProducer.apply(EQ_TEST_LOWER_BOUND_INCLUSIVE)))
|
||||
, null, mock(QueryContext.class)))
|
||||
{
|
||||
assertEquals(results.getMinimum(), results.getCurrent());
|
||||
assertTrue(results.hasNext());
|
||||
|
||||
assertEquals(0L, results.next().token().getLongValue());
|
||||
|
|
@ -186,7 +185,6 @@ public class BalancedTreeIndexSearcherTest extends SAIRandomizedTester
|
|||
.add(Operator.LTE, rawType.decompose(rawValueProducer.apply((short)7))),
|
||||
null, mock(QueryContext.class)))
|
||||
{
|
||||
assertEquals(results.getMinimum(), results.getCurrent());
|
||||
assertTrue(results.hasNext());
|
||||
|
||||
List<Long> actualTokenList = Lists.newArrayList(Iterators.transform(results, key -> key.token().getLongValue()));
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ public class InvertedIndexSearcherTest extends SAIRandomizedTester
|
|||
{
|
||||
try (KeyRangeIterator results = searcher.search(Expression.create(index).add(Operator.EQ, wrap(termsEnum.get(t).left)), null, context))
|
||||
{
|
||||
assertEquals(results.getMinimum(), results.getCurrent());
|
||||
assertTrue(results.hasNext());
|
||||
|
||||
for (int p = 0; p < numPostings; ++p)
|
||||
|
|
@ -129,7 +128,6 @@ public class InvertedIndexSearcherTest extends SAIRandomizedTester
|
|||
|
||||
try (KeyRangeIterator results = searcher.search(Expression.create(index).add(Operator.EQ, wrap(termsEnum.get(t).left)), null, context))
|
||||
{
|
||||
assertEquals(results.getMinimum(), results.getCurrent());
|
||||
assertTrue(results.hasNext());
|
||||
|
||||
// test skipping to the last block
|
||||
|
|
|
|||
|
|
@ -18,11 +18,14 @@
|
|||
package org.apache.cassandra.index.sai.iterators;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.apache.cassandra.index.sai.utils.SAIRandomizedTester;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class AbstractKeyRangeIteratorTester extends SAIRandomizedTester
|
||||
{
|
||||
|
|
@ -36,11 +39,6 @@ public class AbstractKeyRangeIteratorTester extends SAIRandomizedTester
|
|||
return Arrays.stream(intArray).mapToLong(i -> i).toArray();
|
||||
}
|
||||
|
||||
void assertOnError(KeyRangeIterator range)
|
||||
{
|
||||
assertThatThrownBy(() -> LongIterator.convert(range)).isInstanceOf(RuntimeException.class);
|
||||
}
|
||||
|
||||
final KeyRangeIterator buildIntersection(KeyRangeIterator... ranges)
|
||||
{
|
||||
return KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE).add(Arrays.asList(ranges)).build();
|
||||
|
|
@ -66,62 +64,77 @@ public class AbstractKeyRangeIteratorTester extends SAIRandomizedTester
|
|||
return KeyRangeUnionIterator.builder(ranges.length).add(Arrays.asList(ranges)).build();
|
||||
}
|
||||
|
||||
final KeyRangeIterator buildUnion(long[]... ranges)
|
||||
{
|
||||
return buildUnion(toRangeIterator(ranges));
|
||||
}
|
||||
|
||||
final KeyRangeIterator buildConcat(KeyRangeIterator... ranges)
|
||||
static KeyRangeIterator buildConcat(KeyRangeIterator... ranges)
|
||||
{
|
||||
return KeyRangeConcatIterator.builder(ranges.length).add(Arrays.asList(ranges)).build();
|
||||
}
|
||||
|
||||
final KeyRangeIterator buildConcat(long[]... ranges)
|
||||
private static KeyRangeIterator[] toRangeIterator(long[]... ranges)
|
||||
{
|
||||
return buildConcat(toRangeIterator(ranges));
|
||||
return Arrays.stream(ranges).map(AbstractKeyRangeIteratorTester::build).toArray(KeyRangeIterator[]::new);
|
||||
}
|
||||
|
||||
private KeyRangeIterator[] toRangeIterator(long[]... ranges)
|
||||
protected static LongIterator build(long... tokens)
|
||||
{
|
||||
return Arrays.stream(ranges).map(this::build).toArray(KeyRangeIterator[]::new);
|
||||
}
|
||||
|
||||
protected LongIterator build(long... tokens)
|
||||
{
|
||||
return build(tokens, false);
|
||||
}
|
||||
|
||||
protected LongIterator build(long[] tokensA, boolean onErrorA)
|
||||
{
|
||||
LongIterator rangeA = new LongIterator(tokensA);
|
||||
|
||||
if (onErrorA)
|
||||
rangeA.throwsException();
|
||||
|
||||
return rangeA;
|
||||
}
|
||||
|
||||
protected KeyRangeIterator buildOnError(BiFunction<KeyRangeIterator, KeyRangeIterator, KeyRangeIterator> builder, long[] tokensA, long[] tokensB)
|
||||
{
|
||||
return build(builder, tokensA, true, tokensB, true);
|
||||
}
|
||||
|
||||
protected KeyRangeIterator buildOnErrorA(BiFunction<KeyRangeIterator, KeyRangeIterator, KeyRangeIterator> builder, long[] tokensA, long[] tokensB)
|
||||
{
|
||||
return build(builder, tokensA, true, tokensB, false);
|
||||
}
|
||||
|
||||
protected KeyRangeIterator buildOnErrorB(BiFunction<KeyRangeIterator, KeyRangeIterator, KeyRangeIterator> builder, long[] tokensA, long[] tokensB)
|
||||
{
|
||||
return build(builder, tokensA, false, tokensB, true);
|
||||
return new LongIterator(tokens);
|
||||
}
|
||||
|
||||
protected KeyRangeIterator build(BiFunction<KeyRangeIterator, KeyRangeIterator, KeyRangeIterator> builder,
|
||||
long[] tokensA,
|
||||
boolean onErrorA,
|
||||
long[] tokensB,
|
||||
boolean onErrorB)
|
||||
long[] tokensB)
|
||||
{
|
||||
return builder.apply(build(tokensA, onErrorA), build(tokensB, onErrorB));
|
||||
return builder.apply(build(tokensA), build(tokensB));
|
||||
}
|
||||
|
||||
static void validateWithSkipping(KeyRangeIterator ri, long[] totalOrdering)
|
||||
{
|
||||
int count = 0;
|
||||
while (ri.hasNext())
|
||||
{
|
||||
// make sure hasNext plays nice with skipTo
|
||||
if (nextBoolean())
|
||||
ri.hasNext();
|
||||
|
||||
// skipping to the same element should also be a no-op
|
||||
if (nextBoolean())
|
||||
ri.skipTo(LongIterator.fromToken(totalOrdering[count]));
|
||||
|
||||
// skip a few elements
|
||||
if (nextDouble() < 0.1)
|
||||
{
|
||||
int n = nextInt(1, 3);
|
||||
if (count + n < totalOrdering.length)
|
||||
{
|
||||
count += n;
|
||||
ri.skipTo(LongIterator.fromToken(totalOrdering[count]));
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(totalOrdering[count++], ri.next().token().getLongValue());
|
||||
}
|
||||
Assert.assertEquals(totalOrdering.length, count);
|
||||
}
|
||||
|
||||
static Set<Long> toSet(long[] tokens)
|
||||
{
|
||||
return Arrays.stream(tokens).boxed().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a random {Concat,Intersection, Union} iterator, and a long[] of the elements in the iterator.
|
||||
* elements will range from 0..1024.
|
||||
*/
|
||||
static Pair<KeyRangeIterator, long[]> createRandomIterator()
|
||||
{
|
||||
var n = between(0, 3);
|
||||
switch (n)
|
||||
{
|
||||
case 0:
|
||||
return KeyRangeConcatIteratorTest.createRandom();
|
||||
case 1:
|
||||
return KeyRangeIntersectionIteratorTest.createRandom(nextInt(1, 16));
|
||||
case 2:
|
||||
return KeyRangeUnionIteratorTest.createRandom(nextInt(1, 16));
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.index.sai.iterators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ import org.junit.Test;
|
|||
|
||||
import org.apache.cassandra.dht.Murmur3Partitioner;
|
||||
import org.apache.cassandra.index.sai.utils.PrimaryKey;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static org.apache.cassandra.index.sai.iterators.LongIterator.convert;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
|
@ -109,7 +111,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertNotNull(keyIterator);
|
||||
assertEquals(1L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(9L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertEquals(9L, keyIterator.getCount());
|
||||
assertEquals(9L, keyIterator.getMaxKeys());
|
||||
|
||||
for (long i = 1; i < 10; i++)
|
||||
{
|
||||
|
|
@ -196,7 +198,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(10, keyIterator.getCount());
|
||||
assertEquals(10, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -211,7 +213,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(10L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(1, keyIterator.getCount());
|
||||
assertEquals(1, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -226,7 +228,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(10, keyIterator.getCount());
|
||||
assertEquals(10, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -241,7 +243,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(10L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(1, keyIterator.getCount());
|
||||
assertEquals(1, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -257,7 +259,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(10, keyIterator.getCount());
|
||||
assertEquals(10, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -273,7 +275,7 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, keyIterator.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, keyIterator.getMaximum().token().getLongValue());
|
||||
assertTrue(keyIterator.hasNext());
|
||||
assertEquals(10, keyIterator.getCount());
|
||||
assertEquals(10, keyIterator.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -318,37 +320,6 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(convert(1L, 3L, 5L, 7L, 9L), convert(buildIntersection(concatA, concatB)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcatOnError()
|
||||
{
|
||||
assertOnError(buildOnErrorA(this::buildConcat, arr(1L, 2L, 3L), arr(4L, 5L, 6L)));
|
||||
assertOnError(buildOnErrorB(this::buildConcat, arr( 1L, 2L, 3L), arr(4L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcatOfUnionsOnError()
|
||||
{
|
||||
KeyRangeIterator unionA = buildUnion(arr(1L, 2L, 3L), arr(4L));
|
||||
KeyRangeIterator unionB = buildOnErrorB(this::buildUnion, arr(6L), arr(8L, 9L));
|
||||
assertOnError(buildConcat(unionA, unionB));
|
||||
|
||||
unionA = buildOnErrorA(this::buildUnion, arr( 1L, 2L, 3L), arr( 4L));
|
||||
unionB = buildUnion(arr( 5L), arr( 5L, 6L));
|
||||
assertOnError(buildConcat(unionA, unionB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcatOfIntersectionsOnError()
|
||||
{
|
||||
KeyRangeIterator intersectionA = buildOnErrorA(this::buildIntersection, arr(1L, 2L, 3L), arr(2L, 3L, 4L));
|
||||
KeyRangeIterator intersectionB = buildIntersection(arr(6L, 7L, 8L), arr(7L, 8L, 9L));
|
||||
assertOnError(buildConcat(intersectionA, intersectionB));
|
||||
|
||||
intersectionA = buildIntersection(arr( 1L, 2L, 3L), arr( 2L, 3L, 4L));
|
||||
intersectionB = buildOnErrorB(this::buildIntersection, arr( 6L, 7L, 8L, 9L, 10L), arr( 7L, 8L, 9L));
|
||||
assertOnError(buildConcat(intersectionA, intersectionB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedElementsInTheSameIterator()
|
||||
{
|
||||
|
|
@ -424,4 +395,40 @@ public class KeyRangeConcatIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
primaryKeyFactory.create(new Murmur3Partitioner.LongToken(max)),
|
||||
primaryKeyFactory.create(new Murmur3Partitioner.LongToken(min)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom()
|
||||
{
|
||||
for (int testIteration = 0; testIteration < 16; testIteration++)
|
||||
{
|
||||
var p = createRandom();
|
||||
validateWithSkipping(p.left, p.right);
|
||||
}
|
||||
}
|
||||
|
||||
static Pair<KeyRangeIterator, long[]> createRandom()
|
||||
{
|
||||
var ranges = new ArrayList<KeyRangeIterator>();
|
||||
var current = new ArrayList<Long>();
|
||||
var allValues = new ArrayList<Long>();
|
||||
int maxValue = 1024;
|
||||
for (int i = 0; i < maxValue; i++)
|
||||
{
|
||||
allValues.add((long) i);
|
||||
current.add((long) i);
|
||||
if (nextDouble() < 0.05)
|
||||
{
|
||||
ranges.add(build(current.stream().mapToLong(Long::longValue).toArray()));
|
||||
current.clear();
|
||||
}
|
||||
if (nextDouble() < 0.1)
|
||||
i += nextInt(5);
|
||||
}
|
||||
ranges.add(build(current.stream().mapToLong(Long::longValue).toArray()));
|
||||
|
||||
long[] totalOrdered = allValues.stream().mapToLong(Long::longValue).toArray();
|
||||
KeyRangeIterator it = buildConcat(ranges.toArray(KeyRangeIterator[]::new));
|
||||
assertEquals(totalOrdered.length, it.getMaxKeys());
|
||||
return Pair.create(it, totalOrdered);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,18 @@
|
|||
package org.apache.cassandra.index.sai.iterators;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.carrotsearch.hppc.LongHashSet;
|
||||
import com.carrotsearch.hppc.LongSet;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static org.apache.cassandra.index.sai.iterators.LongIterator.convert;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
|
@ -115,23 +118,23 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
assertNotNull(range);
|
||||
|
||||
// first let's skipTo something before range
|
||||
assertEquals(4L, range.skipTo(LongIterator.fromToken(3L)).token().getLongValue());
|
||||
assertEquals(4L, range.getCurrent().token().getLongValue());
|
||||
range.skipTo(LongIterator.fromToken(3L));
|
||||
Assert.assertEquals(4L, range.peek().token().getLongValue());
|
||||
|
||||
// now let's skip right to the send value
|
||||
assertEquals(6L, range.skipTo(LongIterator.fromToken(5L)).token().getLongValue());
|
||||
assertEquals(6L, range.getCurrent().token().getLongValue());
|
||||
range.skipTo(LongIterator.fromToken(5L));
|
||||
Assert.assertEquals(6L, range.peek().token().getLongValue());
|
||||
|
||||
// now right to the element
|
||||
assertEquals(7L, range.skipTo(LongIterator.fromToken(7L)).token().getLongValue());
|
||||
assertEquals(7L, range.getCurrent().token().getLongValue());
|
||||
range.skipTo(LongIterator.fromToken(7L));
|
||||
Assert.assertEquals(7L, range.peek().token().getLongValue());
|
||||
assertEquals(7L, range.next().token().getLongValue());
|
||||
|
||||
assertTrue(range.hasNext());
|
||||
assertEquals(10L, range.getCurrent().token().getLongValue());
|
||||
Assert.assertEquals(10L, range.peek().token().getLongValue());
|
||||
|
||||
// now right after the last element
|
||||
assertNull(range.skipTo(LongIterator.fromToken(11L)));
|
||||
range.skipTo(LongIterator.fromToken(11L));
|
||||
assertFalse(range.hasNext());
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +155,7 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
assertNotNull(tokens);
|
||||
assertEquals(7L, tokens.getMinimum().token().getLongValue());
|
||||
assertEquals(9L, tokens.getMaximum().token().getLongValue());
|
||||
assertEquals(3L, tokens.getCount());
|
||||
assertEquals(3L, tokens.getMaxKeys());
|
||||
|
||||
assertEquals(convert(9L), convert(builder.build()));
|
||||
}
|
||||
|
|
@ -198,7 +201,7 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
FileUtils.closeQuietly(tokens);
|
||||
|
||||
KeyRangeIterator emptyTokens = KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE).build();
|
||||
assertEquals(0, emptyTokens.getCount());
|
||||
assertEquals(0, emptyTokens.getMaxKeys());
|
||||
|
||||
builder = KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE);
|
||||
assertEquals(0L, builder.add((KeyRangeIterator) null).rangeCount());
|
||||
|
|
@ -218,7 +221,7 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
builder.add(single);
|
||||
assertEquals(1L, builder.rangeCount());
|
||||
range = builder.build();
|
||||
assertEquals(0, range.getCount());
|
||||
assertEquals(0, range.getMaxKeys());
|
||||
|
||||
// disjoint case
|
||||
builder = KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE);
|
||||
|
|
@ -298,7 +301,7 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
assertNull(range.getMinimum());
|
||||
assertNull(range.getMaximum());
|
||||
assertFalse(range.hasNext());
|
||||
assertEquals(0, range.getCount());
|
||||
assertEquals(0, range.getMaxKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -343,48 +346,38 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
{
|
||||
for (int attempt = 0; attempt < 16; attempt++)
|
||||
{
|
||||
final int maxRanges = nextInt(2, 16);
|
||||
|
||||
// generate randomize ranges
|
||||
long[][] ranges = new long[maxRanges][];
|
||||
for (int i = 0; i < ranges.length; i++)
|
||||
{
|
||||
int rangeSize = nextInt(16, 512);
|
||||
LongSet range = new LongHashSet(rangeSize);
|
||||
|
||||
for (int j = 0; j < rangeSize; j++)
|
||||
range.add(nextLong(0, 100));
|
||||
|
||||
ranges[i] = range.toArray();
|
||||
Arrays.sort(ranges[i]);
|
||||
}
|
||||
|
||||
List<Long> expected = new ArrayList<>();
|
||||
// determine unique tokens which intersect every range
|
||||
for (long token : ranges[0])
|
||||
{
|
||||
boolean intersectsAll = true;
|
||||
for (int i = 1; i < ranges.length; i++)
|
||||
{
|
||||
if (Arrays.binarySearch(ranges[i], token) < 0)
|
||||
{
|
||||
intersectsAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (intersectsAll)
|
||||
expected.add(token);
|
||||
}
|
||||
|
||||
KeyRangeIterator.Builder builder = KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE);
|
||||
for (long[] range : ranges)
|
||||
builder.add(new LongIterator(range));
|
||||
|
||||
assertEquals(expected, convert(builder.build()));
|
||||
var p = createRandom(nextInt(2, 16));
|
||||
validateWithSkipping(p.left, p.right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a long[][] of random elements, and a long[] of the intersection of those elements
|
||||
*/
|
||||
static Pair<KeyRangeIterator, long[]> createRandom(int nRanges)
|
||||
{
|
||||
// generate randomized ranges
|
||||
long[][] ranges = new long[nRanges][];
|
||||
for (int i = 0; i < ranges.length; i++)
|
||||
{
|
||||
int rangeSize = nextInt(16, 512);
|
||||
LongSet range = new LongHashSet(rangeSize);
|
||||
|
||||
for (int j = 0; j < rangeSize; j++)
|
||||
range.add(nextInt(1024));
|
||||
|
||||
ranges[i] = range.toArray();
|
||||
Arrays.sort(ranges[i]);
|
||||
}
|
||||
var builder = KeyRangeIntersectionIterator.builder(16, Integer.MAX_VALUE);
|
||||
for (long[] range : ranges)
|
||||
builder.add(new LongIterator(range));
|
||||
|
||||
Set<Long> expectedSet = toSet(ranges[0]);
|
||||
IntStream.range(1, ranges.length).forEach(i -> expectedSet.retainAll(toSet(ranges[i])));
|
||||
return Pair.create(builder.build(), expectedSet.stream().mapToLong(Long::longValue).sorted().toArray());
|
||||
}
|
||||
|
||||
// SAI specific tests
|
||||
@Test
|
||||
public void testSelectiveIntersection()
|
||||
|
|
@ -403,34 +396,4 @@ public class KeyRangeIntersectionIteratorTest extends AbstractKeyRangeIteratorTe
|
|||
|
||||
assertEquals(convert(2L, 4L, 6L), convert(intersection));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersectionOfUnionsOnError()
|
||||
{
|
||||
// intersection of two unions
|
||||
KeyRangeIterator unionA = buildOnErrorB(this::buildUnion, arr(1L, 2L, 3L), arr(5L, 6L, 7L));
|
||||
KeyRangeIterator unionB = buildUnion(arr(2L, 4L, 6L), arr(5L, 7L, 9L));
|
||||
assertOnError(buildIntersection(unionA, unionB));
|
||||
|
||||
// intersection of union and intersection
|
||||
KeyRangeIterator unionC = buildOnErrorB(this::buildUnion, arr(2L, 4L, 6L), arr(5L, 6L, 9L));
|
||||
KeyRangeIterator intersectionA = buildIntersection(arr(3L, 4L, 6L, 9L), arr(2L, 3L, 6L, 9L));
|
||||
assertOnError(buildIntersection(unionC, intersectionA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersectionOfIntersectionsOnError()
|
||||
{
|
||||
KeyRangeIterator intersectionA = buildIntersection(arr(1L, 2L, 3L, 6L), arr(2L, 3L, 6L));
|
||||
KeyRangeIterator intersectionB = buildOnErrorA(this::buildIntersection, arr(2L, 4L, 6L), arr(5L, 6L, 7L, 9L));
|
||||
assertOnError(buildIntersection(intersectionA, intersectionB));
|
||||
|
||||
intersectionA = buildOnErrorB(this::buildIntersection, arr(1L, 2L, 3L, 4L, 5L), arr(2L, 3L, 4L));
|
||||
intersectionB = buildIntersection(arr(1L, 2L, 3L, 4L, 6L), arr(2L, 3L, 4L, 7L, 9L));
|
||||
assertOnError(buildIntersection(intersectionA, intersectionB));
|
||||
|
||||
intersectionA = buildOnError(this::buildIntersection, arr(1L, 2L, 3L, 5L), arr(3L, 4L));
|
||||
intersectionB = buildIntersection(arr(1L, 2L, 3L, 4L, 6L), arr(2L, 3L, 4L, 7L, 9L));
|
||||
assertOnError(buildIntersection(intersectionA, intersectionB));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,15 @@ package org.apache.cassandra.index.sai.iterators;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static org.apache.cassandra.index.sai.iterators.LongIterator.convert;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
|
@ -106,50 +109,38 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
@Test
|
||||
public void testRandomSequences()
|
||||
{
|
||||
long[][] values = new long[getRandom().nextIntBetween(1, 20)][];
|
||||
int numTests = getRandom().nextIntBetween(10, 20);
|
||||
|
||||
for (int tests = 0; tests < numTests; tests++)
|
||||
for (int testIteration = 0; testIteration < 16; testIteration++)
|
||||
{
|
||||
KeyRangeUnionIterator.Builder builder = KeyRangeUnionIterator.builder(16);
|
||||
int totalCount = 0;
|
||||
|
||||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
long[] part = new long[getRandom().nextIntBetween(1, 500)];
|
||||
for (int j = 0; j < part.length; j++)
|
||||
part[j] = getRandom().nextLong();
|
||||
|
||||
// all the parts have to be sorted to mimic SSTable
|
||||
Arrays.sort(part);
|
||||
|
||||
values[i] = part;
|
||||
builder.add(new LongIterator(part));
|
||||
totalCount += part.length;
|
||||
}
|
||||
|
||||
long[] totalOrdering = new long[totalCount];
|
||||
int index = 0;
|
||||
|
||||
for (long[] part : values)
|
||||
{
|
||||
for (long value : part)
|
||||
totalOrdering[index++] = value;
|
||||
}
|
||||
|
||||
Arrays.sort(totalOrdering);
|
||||
|
||||
int count = 0;
|
||||
KeyRangeIterator tokens = builder.build();
|
||||
|
||||
Assert.assertNotNull(tokens);
|
||||
while (tokens.hasNext())
|
||||
assertEquals(totalOrdering[count++], tokens.next().token().getLongValue());
|
||||
|
||||
assertEquals(totalCount, count);
|
||||
var p = createRandom(nextInt(1, 20));
|
||||
validateWithSkipping(p.left, p.right);
|
||||
}
|
||||
}
|
||||
|
||||
static Pair<KeyRangeIterator, long[]> createRandom(int nRanges)
|
||||
{
|
||||
long[][] values = new long[nRanges][];
|
||||
KeyRangeUnionIterator.Builder builder = KeyRangeUnionIterator.builder(10);
|
||||
var allValues = new HashSet<Long>();
|
||||
// add a random number of random values
|
||||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
int partLength = nextInt(1, 500);
|
||||
var part = new HashSet<Long>(partLength);
|
||||
for (int j = 0; j < partLength; j++)
|
||||
{
|
||||
long m = nextLong(0, 1024);
|
||||
part.add(m);
|
||||
allValues.add(m);
|
||||
}
|
||||
|
||||
// all the parts have to be sorted to mimic SSTable
|
||||
builder.add(new LongIterator(part.stream().mapToLong(Long::longValue).sorted().toArray()));
|
||||
}
|
||||
long[] totalOrdering = allValues.stream().mapToLong(Long::longValue).sorted().toArray();
|
||||
KeyRangeIterator tokens = builder.build();
|
||||
return Pair.create(tokens, totalOrdering);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinMaxAndCount()
|
||||
{
|
||||
|
|
@ -167,7 +158,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
Assert.assertNotNull(tokens);
|
||||
assertEquals(1L, tokens.getMinimum().token().getLongValue());
|
||||
assertEquals(9L, tokens.getMaximum().token().getLongValue());
|
||||
assertEquals(9L, tokens.getCount());
|
||||
assertEquals(9L, tokens.getMaxKeys());
|
||||
|
||||
for (long i = 1; i < 10; i++)
|
||||
{
|
||||
|
|
@ -202,7 +193,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(4L, builder.rangeIterators.get(1).getMinimum().token().getLongValue());
|
||||
assertEquals(7L, builder.rangeIterators.get(2).getMinimum().token().getLongValue());
|
||||
|
||||
KeyRangeIterator tokens = KeyRangeUnionIterator.build(new ArrayList<KeyRangeIterator>()
|
||||
KeyRangeIterator tokens = KeyRangeUnionIterator.build(new ArrayList<>()
|
||||
{{
|
||||
add(new LongIterator(new long[]{1L, 2L, 4L}));
|
||||
add(new LongIterator(new long[]{3L, 5L, 6L}));
|
||||
|
|
@ -213,7 +204,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
FileUtils.closeQuietly(tokens);
|
||||
|
||||
KeyRangeIterator emptyTokens = KeyRangeUnionIterator.builder(16).build();
|
||||
assertEquals(0, emptyTokens.getCount());
|
||||
assertEquals(0, emptyTokens.getMaxKeys());
|
||||
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
assertEquals(0L, builder.add((KeyRangeIterator) null).rangeCount());
|
||||
|
|
@ -281,27 +272,27 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
for (int i = 0; i <= 3; i++)
|
||||
{
|
||||
Assert.assertTrue(tokens.hasNext());
|
||||
assertEquals(i, tokens.getCurrent().token().getLongValue());
|
||||
assertEquals(i, tokens.peek().token().getLongValue());
|
||||
assertEquals(i, tokens.next().token().getLongValue());
|
||||
}
|
||||
}
|
||||
|
||||
try (LongIterator tokens = new LongIterator(new long[] { 0L, 1L, 3L, 5L }))
|
||||
{
|
||||
assertEquals(3L, tokens.skipTo(LongIterator.fromToken(2L)).token().getLongValue());
|
||||
tokens.skipTo(LongIterator.fromToken(2L));
|
||||
Assert.assertTrue(tokens.hasNext());
|
||||
assertEquals(3L, tokens.getCurrent().token().getLongValue());
|
||||
assertEquals(3L, tokens.peek().token().getLongValue());
|
||||
assertEquals(3L, tokens.next().token().getLongValue());
|
||||
|
||||
assertEquals(5L, tokens.skipTo(LongIterator.fromToken(5L)).token().getLongValue());
|
||||
tokens.skipTo(LongIterator.fromToken(5L));
|
||||
Assert.assertTrue(tokens.hasNext());
|
||||
assertEquals(5L, tokens.getCurrent().token().getLongValue());
|
||||
assertEquals(5L, tokens.peek().token().getLongValue());
|
||||
assertEquals(5L, tokens.next().token().getLongValue());
|
||||
}
|
||||
|
||||
try (LongIterator empty = LongIterator.newEmptyIterator())
|
||||
{
|
||||
Assert.assertNull(empty.skipTo(LongIterator.fromToken(3L)));
|
||||
empty.skipTo(LongIterator.fromToken(3L));
|
||||
Assert.assertFalse(empty.hasNext());
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +311,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, range.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(10, range.getCount());
|
||||
assertEquals(10, range.getMaxKeys());
|
||||
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
builder.add(LongIterator.newEmptyIterator());
|
||||
|
|
@ -329,7 +320,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, range.getMinimum().token().getLongValue());
|
||||
assertEquals(10L, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(1, range.getCount());
|
||||
assertEquals(1, range.getMaxKeys());
|
||||
|
||||
// non-empty, then empty
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
|
|
@ -340,7 +331,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10, range.getMinimum().token().getLongValue());
|
||||
assertEquals(19, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(10, range.getCount());
|
||||
assertEquals(10, range.getMaxKeys());
|
||||
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
builder.add(new LongIterator(new long[] {10}));
|
||||
|
|
@ -349,7 +340,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, range.getMinimum().token().getLongValue());
|
||||
assertEquals(10L, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(1, range.getCount());
|
||||
assertEquals(1, range.getMaxKeys());
|
||||
|
||||
// empty, then non-empty then empty again
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
|
|
@ -361,7 +352,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, range.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(10, range.getCount());
|
||||
assertEquals(10, range.getMaxKeys());
|
||||
|
||||
// non-empty, empty, then non-empty again
|
||||
builder = KeyRangeUnionIterator.builder(16);
|
||||
|
|
@ -374,7 +365,7 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertEquals(10L, range.getMinimum().token().getLongValue());
|
||||
assertEquals(19L, range.getMaximum().token().getLongValue());
|
||||
Assert.assertTrue(range.hasNext());
|
||||
assertEquals(10, range.getCount());
|
||||
assertEquals(10, range.getMaxKeys());
|
||||
}
|
||||
|
||||
// SAI specific tests
|
||||
|
|
@ -397,54 +388,28 @@ public class KeyRangeUnionIteratorTest extends AbstractKeyRangeIteratorTester
|
|||
assertSame(KeyRangeUnionIterator.class, union.getClass());
|
||||
|
||||
// union of one intersected intersection and one non-intersected intersection
|
||||
intersectionA = buildIntersection(arr(1L, 2L, 3L), arr(2L, 3L, 4L ));
|
||||
intersectionB = buildIntersection(arr(6L, 7L, 8L), arr(10L ));
|
||||
intersectionA = buildIntersection(arr(1L, 2L, 3L), arr(2L, 3L, 4L));
|
||||
intersectionB = buildIntersection(arr(6L, 7L, 8L), arr(10L));
|
||||
|
||||
union = buildUnion(intersectionA, intersectionB);
|
||||
assertEquals(convert(2L, 3L), convert(union));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionOnError()
|
||||
public void testUnionOfRandom()
|
||||
{
|
||||
assertOnError(buildOnError(this::buildUnion, arr(1L, 3L, 4L ), arr(7L, 8L)));
|
||||
assertOnError(buildOnErrorA(this::buildUnion, arr(1L, 3L, 4L ), arr(4L, 5L)));
|
||||
assertOnError(buildOnErrorB(this::buildUnion, arr(1L), arr(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionOfIntersectionsOnError()
|
||||
{
|
||||
KeyRangeIterator intersectionA = buildIntersection(arr(1L, 2L, 3L, 6L), arr(2L, 3L, 6L));
|
||||
KeyRangeIterator intersectionB = buildOnErrorA(this::buildIntersection, arr(2L, 4L, 6L), arr(5L, 6L, 7L, 9L));
|
||||
assertOnError(buildUnion(intersectionA, intersectionB));
|
||||
|
||||
intersectionA = buildOnErrorB(this::buildIntersection, arr(1L, 2L, 3L, 4L, 5L), arr(2L, 3L, 5L));
|
||||
intersectionB = buildIntersection(arr(2L, 4L, 5L), arr(5L, 6L, 7L));
|
||||
assertOnError(buildUnion(intersectionA, intersectionB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionOfUnionsOnError()
|
||||
{
|
||||
KeyRangeIterator unionA = buildUnion(arr(1L, 2L, 3L, 6L), arr(6L, 7L, 8L));
|
||||
KeyRangeIterator unionB = buildOnErrorA(this::buildUnion, arr(2L, 4L, 6L), arr (6L, 7L, 9L));
|
||||
assertOnError(buildUnion(unionA, unionB));
|
||||
|
||||
unionA = buildOnErrorB(this::buildUnion, arr(1L, 2L, 3L), arr(3L, 7L, 8L));
|
||||
unionB = buildUnion(arr(2L, 4L, 5L), arr (5L, 7L, 9L));
|
||||
assertOnError(buildUnion(unionA, unionB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionOfMergingOnError()
|
||||
{
|
||||
KeyRangeIterator mergingA = buildConcat(arr(1L, 2L, 3L, 6L), arr(6L, 7L, 8L));
|
||||
KeyRangeIterator mergingB = buildOnErrorA(this::buildConcat, arr(2L, 4L, 6L), arr (6L, 7L, 9L));
|
||||
assertOnError(buildUnion(mergingA, mergingB));
|
||||
|
||||
mergingA = buildOnErrorB(this::buildConcat, arr(1L, 2L, 3L), arr(3L, 7L, 8L));
|
||||
mergingB = buildConcat(arr(2L, 4L, 5L), arr (5L, 7L, 9L));
|
||||
assertOnError(buildUnion(mergingA, mergingB));
|
||||
for (int testIteration = 0; testIteration < 16; testIteration++)
|
||||
{
|
||||
var allValues = new HashSet<Long>();
|
||||
var builder = KeyRangeUnionIterator.builder(10);
|
||||
for (int i = 0; i < nextInt(2, 3); i++)
|
||||
{
|
||||
var p = createRandomIterator();
|
||||
builder.add(p.left);
|
||||
allValues.addAll(Arrays.stream(p.right).boxed().collect(Collectors.toList()));
|
||||
}
|
||||
long[] totalOrdered = allValues.stream().mapToLong(Long::longValue).sorted().toArray();
|
||||
validateWithSkipping(builder.build(), totalOrdered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ public class LongIterator extends KeyRangeIterator
|
|||
private final List<PrimaryKey> keys;
|
||||
private int currentIdx = 0;
|
||||
|
||||
/**
|
||||
* whether LongIterator should throw exception during iteration.
|
||||
*/
|
||||
private boolean shouldThrow = false;
|
||||
|
||||
public static LongIterator newEmptyIterator()
|
||||
{
|
||||
return new LongIterator();
|
||||
|
|
@ -54,18 +49,9 @@ public class LongIterator extends KeyRangeIterator
|
|||
this.keys.add(fromToken(token));
|
||||
}
|
||||
|
||||
public void throwsException()
|
||||
{
|
||||
this.shouldThrow = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrimaryKey computeNext()
|
||||
{
|
||||
// throws exception if it's last element or chosen 1 out of n
|
||||
if (shouldThrow && (currentIdx >= keys.size() - 1 || SAITester.getRandom().nextInt(keys.size()) == 0))
|
||||
throw new RuntimeException("injected exception");
|
||||
|
||||
if (currentIdx >= keys.size())
|
||||
return endOfData();
|
||||
|
||||
|
|
@ -75,14 +61,11 @@ public class LongIterator extends KeyRangeIterator
|
|||
@Override
|
||||
protected void performSkipTo(PrimaryKey nextKey)
|
||||
{
|
||||
for (int i = currentIdx == 0 ? 0 : currentIdx - 1; i < keys.size(); i++)
|
||||
for ( ; currentIdx < keys.size(); currentIdx++)
|
||||
{
|
||||
PrimaryKey token = keys.get(i);
|
||||
PrimaryKey token = keys.get(currentIdx);
|
||||
if (token.compareTo(nextKey) >= 0)
|
||||
{
|
||||
currentIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,16 @@ public class SAIRandomizedTester extends SAITester
|
|||
return getRandom().nextIntBetween(min, max - 1);
|
||||
}
|
||||
|
||||
public static double nextDouble()
|
||||
{
|
||||
return getRandom().nextDouble();
|
||||
}
|
||||
|
||||
public static boolean nextBoolean()
|
||||
{
|
||||
return getRandom().nextBoolean();
|
||||
}
|
||||
|
||||
public static long between(long min, long max)
|
||||
{
|
||||
return randomLongBetween(min, max);
|
||||
|
|
|
|||
Loading…
Reference in New Issue