This is an automated email from the ASF dual-hosted git repository. maedhroz pushed a commit to branch cassandra-5.0 in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cassandra-5.0 by this push: new 87f2a7b763 Avoid over-skipping of key iterators from static column indexes during mixed intersections 87f2a7b763 is described below commit 87f2a7b7637a346cdeea23e0b74d9968ccfca619 Author: Caleb Rackliffe <calebrackli...@gmail.com> AuthorDate: Fri Jan 19 04:39:02 2024 -0600 Avoid over-skipping of key iterators from static column indexes during mixed intersections patch by Caleb Rackliffe; reviewed by Piotr Kołaczkowski and Alex Petrov for CASSANDRA-19278 Co-authored-by: Caleb Rackliffe <calebrackli...@gmail.com> Co-authored-by: Piotr Kołaczkowski <pkola...@gmail.com> --- CHANGES.txt | 1 + .../cassandra/index/sai/disk/RowMapping.java | 49 ++-- .../index/sai/disk/v1/MemtableIndexWriter.java | 31 +-- .../iterators/KeyRangeIntersectionIterator.java | 114 ++++++--- .../index/sai/iterators/KeyRangeIterator.java | 8 +- .../cassandra/index/sai/utils/PrimaryKey.java | 53 +++- .../index/sai/cql/RandomIntersectionTest.java | 227 ----------------- .../index/sai/cql/StaticColumnIndexTest.java | 21 +- .../cql/intersection/RandomIntersectionTester.java | 270 +++++++++++++++++++++ .../intersection/RandomMixedIntersectionTest.java | 53 ++++ .../RandomMixedPartitionIntersectionTest.java | 53 ++++ .../RandomRegularIntersectionTest.java | 53 ++++ .../RandomRegularPartitionIntersectionTest.java | 53 ++++ .../intersection/RandomStaticIntersectionTest.java | 53 ++++ .../RandomStaticPartitionIntersectionTest.java | 53 ++++ .../TwoRegularOneStaticIntersectionTest.java | 53 ++++ ...oRegularOneStaticPartitionIntersectionTest.java | 53 ++++ .../cassandra/index/sai/utils/PrimaryKeyTest.java | 3 +- 18 files changed, 897 insertions(+), 304 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 73b0ecdea9..90cb544d10 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0-beta2 + * Avoid over-skipping of key iterators from static column indexes during mixed intersections (CASSANDRA-19278) * Make concurrent_index_builders configurable at runtime (CASSANDRA-19266) * Fix storage_compatibility_mode for streaming (CASSANDRA-19126) * Add support of vector type to cqlsh COPY command (CASSANDRA-19118) diff --git a/src/java/org/apache/cassandra/index/sai/disk/RowMapping.java b/src/java/org/apache/cassandra/index/sai/disk/RowMapping.java index ec122ae006..43bb79b91b 100644 --- a/src/java/org/apache/cassandra/index/sai/disk/RowMapping.java +++ b/src/java/org/apache/cassandra/index/sai/disk/RowMapping.java @@ -70,12 +70,6 @@ public class RowMapping { return -1; } - - @Override - public int size() - { - return 0; - } }; private final InMemoryTrie<Long> rowMapping = new InMemoryTrie<>(BufferType.OFF_HEAP); @@ -84,10 +78,14 @@ public class RowMapping public PrimaryKey minKey; public PrimaryKey maxKey; + public PrimaryKey minStaticKey; + public PrimaryKey maxStaticKey; public long maxSSTableRowId = -1; + public long maxStaticSSTableRowId = -1; - public int count; + public int rowCount; + public int staticRowCount; private RowMapping() {} @@ -171,20 +169,32 @@ public class RowMapping rowMapping.putSingleton(key, sstableRowId, OVERWRITE_TRANSFORMER); - maxSSTableRowId = Math.max(maxSSTableRowId, sstableRowId); - - // data is written in token sorted order - if (minKey == null) - minKey = key; - maxKey = key; - count++; + // Data is written in primary key order. If a schema contains clustering keys, it may also contain static + // columns. We track min, max, and count for static keys separately here so that we can pass them to the segment + // metadata for indexes on static columns. + if (key.kind() == PrimaryKey.Kind.STATIC) + { + if (minStaticKey == null) + minStaticKey = key; + maxStaticKey = key; + staticRowCount++; + maxStaticSSTableRowId = Math.max(maxStaticSSTableRowId, sstableRowId); + } + else + { + if (minKey == null) + minKey = key; + maxKey = key; + rowCount++; + maxSSTableRowId = Math.max(maxSSTableRowId, sstableRowId); + } } /** - * Returns the SSTable row Id for a {@link PrimaryKey} + * Returns the SSTable row ID for a {@link PrimaryKey} * * @param key the {@link PrimaryKey} - * @return a valid SSTable row Id for the {@link PrimaryKey} or -1 if the {@link PrimaryKey} doesn't exist + * @return a valid SSTable row ID for the {@link PrimaryKey} or -1 if the {@link PrimaryKey} doesn't exist * in the {@link RowMapping} */ public int get(PrimaryKey key) @@ -193,13 +203,8 @@ public class RowMapping return sstableRowId == null ? -1 : Math.toIntExact(sstableRowId); } - public int size() - { - return count; - } - public boolean hasRows() { - return maxSSTableRowId >= 0; + return maxSSTableRowId >= 0 || maxStaticSSTableRowId >= 0; } } diff --git a/src/java/org/apache/cassandra/index/sai/disk/v1/MemtableIndexWriter.java b/src/java/org/apache/cassandra/index/sai/disk/v1/MemtableIndexWriter.java index 65e42ef91e..2bf6639e16 100644 --- a/src/java/org/apache/cassandra/index/sai/disk/v1/MemtableIndexWriter.java +++ b/src/java/org/apache/cassandra/index/sai/disk/v1/MemtableIndexWriter.java @@ -155,15 +155,15 @@ public class MemtableIndexWriter implements PerColumnIndexWriter return 0; } + PrimaryKey minKey = indexTermType.columnMetadata().isStatic() ? rowMapping.minStaticKey : rowMapping.minKey; + PrimaryKey maxKey = indexTermType.columnMetadata().isStatic() ? rowMapping.maxStaticKey : rowMapping.maxKey; + // During index memtable flush, the data is sorted based on terms. SegmentMetadata metadata = new SegmentMetadata(0, numRows, - terms.getMinSSTableRowId(), - terms.getMaxSSTableRowId(), - rowMapping.minKey, - rowMapping.maxKey, - terms.getMinTerm(), - terms.getMaxTerm(), + terms.getMinSSTableRowId(), terms.getMaxSSTableRowId(), + minKey, maxKey, + terms.getMinTerm(), terms.getMaxTerm(), indexMetas); try (MetadataWriter metadataWriter = new MetadataWriter(indexDescriptor.openPerIndexOutput(IndexComponent.META, indexIdentifier))) @@ -176,18 +176,19 @@ public class MemtableIndexWriter implements PerColumnIndexWriter private void flushVectorIndex(long startTime, Stopwatch stopwatch) throws IOException { - SegmentMetadata.ComponentMetadataMap metadataMap = memtable.writeDirect(indexDescriptor, indexIdentifier, rowMapping::get); + int rowCount = indexTermType.columnMetadata().isStatic() ? rowMapping.staticRowCount : rowMapping.rowCount; + PrimaryKey minKey = indexTermType.columnMetadata().isStatic() ? rowMapping.minStaticKey : rowMapping.minKey; + PrimaryKey maxKey = indexTermType.columnMetadata().isStatic() ? rowMapping.maxStaticKey : rowMapping.maxKey; + long maxSSTableRowId = indexTermType.columnMetadata().isStatic() ? rowMapping.maxStaticSSTableRowId : rowMapping.maxSSTableRowId; - completeIndexFlush(rowMapping.size(), startTime, stopwatch); + SegmentMetadata.ComponentMetadataMap metadataMap = memtable.writeDirect(indexDescriptor, indexIdentifier, rowMapping::get); + completeIndexFlush(rowCount, startTime, stopwatch); SegmentMetadata metadata = new SegmentMetadata(0, - rowMapping.size(), - 0, - rowMapping.maxSSTableRowId, - rowMapping.minKey, - rowMapping.maxKey, - ByteBufferUtil.bytes(0), - ByteBufferUtil.bytes(0), + rowCount, + 0, maxSSTableRowId, + minKey, maxKey, + ByteBufferUtil.bytes(0), ByteBufferUtil.bytes(0), metadataMap); try (MetadataWriter writer = new MetadataWriter(indexDescriptor.openPerIndexOutput(IndexComponent.META, indexIdentifier))) diff --git a/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIntersectionIterator.java b/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIntersectionIterator.java index 6e297b338c..f674c04dfc 100644 --- a/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIntersectionIterator.java +++ b/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIntersectionIterator.java @@ -28,13 +28,19 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.index.sai.utils.PrimaryKey; +import org.apache.cassandra.index.sai.utils.PrimaryKey.Kind; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.tracing.Tracing; +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 which limits - * the number of ranges that will be included in the intersection. This currently defaults to 2. + * 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. + * <p> + * Intersection only works for ranges that are compatible according to {@link PrimaryKey.Kind#isIntersectable(Kind)}. */ public class KeyRangeIntersectionIterator extends KeyRangeIterator { @@ -56,52 +62,82 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator @Override protected PrimaryKey computeNext() { - // Range iterator that has been advanced in the previous cycle of the outer loop. - // Initially there hasn't been the previous cycle, so set to null. - int alreadyAvanced = -1; - - // The highest primary key seen on any range iterator so far. + // 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. - PrimaryKey highestKey = getCurrent(); + // 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(); outer: - // We need to check if highestKey exceeds the maximum because the maximum is - // the lowest value maximum of all the ranges. As a result any value could - // potentially exceed it. + // After advancing one iterator, we must try to advance all the other iterators that got behind, + // so they catch up to it. Note that we will not advance the iterators for static columns + // as long as they point to the partition of the highest key. (This is because STATIC primary keys + // compare to other keys only by partition.) This loop continues until all iterators point to the same key, + // or if we run out of keys on any of them, or if we exceed the maximum key. + // There is no point in iterating after maximum, because no keys will match beyond that point. while (highestKey != null && highestKey.compareTo(getMaximum()) <= 0) { - // Try advance all iterators to the highest key seen so far. + // Try to advance all iterators to the highest key seen so far. // Once this inner loop finishes normally, all iterators are guaranteed to be at the same value. - for (int index = 0; index < ranges.size(); index++) + for (KeyRangeIterator range : ranges) { - if (index != alreadyAvanced) + if (range.getCurrent().compareTo(highestKey) < 0) { - KeyRangeIterator range = ranges.get(index); - PrimaryKey nextKey = nextOrNull(range, highestKey); - if (nextKey == null || nextKey.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); + + // 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 + // keys to avoid retrieving and post-filtering entire partitions. + if (nextKey == null || nextKey.compareToStrict(highestKey) > 0) { // We jumped over the highest key seen so far, so make it the new highest key. highestKey = nextKey; - // Remember this iterator to avoid advancing it again, because it is already at the highest key - alreadyAvanced = index; - // This iterator jumped over, so the other iterators are lagging behind now, + + // This iterator jumped over, so the other iterators might be lagging behind now, // including the ones already advanced in the earlier cycles of the inner loop. - // Therefore, restart the inner loop in order to advance - // the other iterators except this one to match the new highest key. + // Therefore, restart the inner loop in order to advance the lagging iterators. continue outer; } - assert nextKey.compareTo(highestKey) == 0: - String.format("skipped to an item smaller than the target; " + - "iterator: %s, target key: %s, returned key: %s", range, highestKey, nextKey); + assert nextKey.compareTo(highestKey) == 0 : + String.format("Skipped to a key smaller than the target! " + + "iterator: %s, target key: %s, returned key: %s", range, highestKey, nextKey); } } - // If we reached here, next() has been called at least once on each range iterator and - // the last call to next() on each iterator returned a value equal to the highestKey. + + // 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; } + return endOfData(); } + /** + * Advances the iterator of one range to the next item, which becomes the highest seen so far. + * Iterators pointing to STATIC keys are advanced only if no non-STATIC keys have been advanced. + * + * @return the next highest key or null if the iterator has reached the end + */ + private @Nullable PrimaryKey advanceOneRange() + { + PrimaryKey highestKey = getCurrent(); + + 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); + + throw new IllegalStateException("There should be at least one range to advance!"); + } + @Override protected void performSkipTo(PrimaryKey nextKey) { @@ -236,6 +272,20 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator if (ranges.size() == 1) return ranges.get(0); + // Make sure intersection is supported on the ranges provided: + PrimaryKey.Kind firstKind = null; + + for (KeyRangeIterator range : ranges) + { + PrimaryKey key = range.getCurrent(); + + if (key != null) + if (firstKind == null) + firstKind = key.kind(); + else if (!firstKind.isIntersectable(key.kind())) + throw new IllegalArgumentException("Cannot intersect " + firstKind + " and " + key.kind() + " ranges!"); + } + return new KeyRangeIntersectionIterator(statistics, ranges); } @@ -277,18 +327,18 @@ public class KeyRangeIntersectionIterator extends KeyRangeIterator /** * Ranges are overlapping the following cases: - * + * <p> * * When they have a common subrange: - * + * <p> * min b.current max b.max * +---------|--------------+------------| - * + * <p> * b.current min max b.max * |--------------+---------+------------| - * + * <p> * min b.current b.max max * +----------|-------------|------------+ - * + * <p> * * If either range is empty, they're disjoint. */ diff --git a/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIterator.java b/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIterator.java index cb6b69a5a9..da1b8c2a04 100644 --- a/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIterator.java +++ b/src/java/org/apache/cassandra/index/sai/iterators/KeyRangeIterator.java @@ -211,19 +211,19 @@ public abstract class KeyRangeIterator extends AbstractGuavaIterator<PrimaryKey> } } - protected static <T extends Comparable<T>> T nullSafeMin(T a, T b) + protected static PrimaryKey nullSafeMin(PrimaryKey a, PrimaryKey b) { if (a == null) return b; if (b == null) return a; - return a.compareTo(b) > 0 ? b : a; + return a.compareToStrict(b) > 0 ? b : a; } - protected static <T extends Comparable<T>> T nullSafeMax(T a, T b) + protected static PrimaryKey nullSafeMax(PrimaryKey a, PrimaryKey b) { if (a == null) return b; if (b == null) return a; - return a.compareTo(b) > 0 ? a : b; + return a.compareToStrict(b) > 0 ? a : b; } } diff --git a/src/java/org/apache/cassandra/index/sai/utils/PrimaryKey.java b/src/java/org/apache/cassandra/index/sai/utils/PrimaryKey.java index f3492927eb..c079a66c4c 100644 --- a/src/java/org/apache/cassandra/index/sai/utils/PrimaryKey.java +++ b/src/java/org/apache/cassandra/index/sai/utils/PrimaryKey.java @@ -20,7 +20,6 @@ package org.apache.cassandra.index.sai.utils; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Objects; - import java.util.stream.Collectors; import org.apache.cassandra.db.BufferDecoratedKey; @@ -59,6 +58,18 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable { this.hasClustering = hasClustering; } + + public boolean isIntersectable(Kind other) + { + if (this == TOKEN) + return other == TOKEN; + else if (this == SKINNY) + return other == SKINNY; + else if (this == WIDE || this == STATIC) + return other == WIDE || other == STATIC; + + throw new AssertionError("Unknown Kind: " + other); + } } class Factory @@ -315,6 +326,15 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable return 0; } + @Override + public int compareToStrict(PrimaryKey o) + { + int cmp = compareTo(o); + // Always order this STATIC key before a WIDE key in the same partition, as this corresponds to the + // order of the corresponding row IDs in an on-disk postings list. + return o.kind() == Kind.WIDE && cmp == 0 ? -1 : cmp; + } + @Override public int hashCode() { @@ -326,6 +346,12 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable { return String.format("PrimaryKey: { token: %s, partition: %s, clustering: STATIC } ", token(), partitionKey()); } + + @Override + public PrimaryKey toStatic() + { + return this; + } } class WidePrimaryKey extends SkinnyPrimaryKey @@ -376,6 +402,15 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable return clusteringComparator.compare(clustering(), o.clustering()); } + @Override + public int compareToStrict(PrimaryKey o) + { + int cmp = compareTo(o); + // Always order this WIDE key before a STATIC key in the same partition, as this corresponds to the + // order of the corresponding row IDs in an on-disk postings list. + return o.kind() == Kind.STATIC && cmp == 0 ? 1 : cmp; + } + @Override public int hashCode() { @@ -393,6 +428,12 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable .map(ByteBufferUtil::bytesToHex) .collect(Collectors.joining(", "))); } + + @Override + public PrimaryKey toStatic() + { + return new StaticPrimaryKey(partitionKey); + } } } @@ -438,4 +479,14 @@ public interface PrimaryKey extends Comparable<PrimaryKey>, ByteComparable * @throws UnsupportedOperationException for {@link PrimaryKey} implementations that are not byte-comparable */ ByteSource asComparableBytes(ByteComparable.Version version); + + default PrimaryKey toStatic() + { + throw new UnsupportedOperationException("Only STATIC and WIDE keys can be converted to STATIC"); + } + + default int compareToStrict(PrimaryKey o) + { + return compareTo(o); + } } diff --git a/test/unit/org/apache/cassandra/index/sai/cql/RandomIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/RandomIntersectionTest.java deleted file mode 100644 index 3ab0b80e35..0000000000 --- a/test/unit/org/apache/cassandra/index/sai/cql/RandomIntersectionTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.index.sai.cql; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import org.apache.cassandra.index.sai.utils.SAIRandomizedTester; - -@RunWith(Parameterized.class) -public class RandomIntersectionTest extends SAIRandomizedTester -{ - private static final Object[][] EMPTY_ROWS = new Object[][]{}; - - @Parameterized.Parameter - public String testName; - - @Parameterized.Parameter(1) - public boolean partitionRestricted; - - @Parameterized.Parameter(2) - public boolean largePartition; - - @Parameterized.Parameter(3) - public boolean v1Cardinality; - - @Parameterized.Parameter(4) - public boolean v2Cardinality; - - @Parameterized.Parameters(name = "{0}") - public static List<Object[]> parameters() - { - List<Object[]> parameters = new LinkedList<>(); - - parameters.add(new Object[]{ "Large partition restricted high high", true, true, true, true }); - parameters.add(new Object[]{ "Large partition restricted low low", true, true, false, false }); - parameters.add(new Object[]{ "Large partition restricted high low", true, true, true, false }); - parameters.add(new Object[]{ "Large partition unrestricted high high", false, true, true, true }); - parameters.add(new Object[]{ "Large partition unrestricted low low", false, true, false, false }); - parameters.add(new Object[]{ "Large partition unrestricted high low", false, true, true, false }); - parameters.add(new Object[]{ "Small partition restricted high high", true, false, true, true }); - parameters.add(new Object[]{ "Small partition restricted low low", true, false, false, false }); - parameters.add(new Object[]{ "Small partition restricted high low", true, false, true, false }); - parameters.add(new Object[]{ "Small partition unrestricted high high", false, false, true, true }); - parameters.add(new Object[]{ "Small partition unrestricted low low", false, false, false, false }); - parameters.add(new Object[]{ "Small partition unrestricted high low", false, false, true, false }); - - return parameters; - } - - private int numRows; - - @Before - public void createTableAndIndexes() - { - createTable("CREATE TABLE %s (pk int, ck int, v1 int, v2 int, PRIMARY KEY(pk, ck))"); - createIndex("CREATE INDEX ON %s(v1) USING 'sai'"); - createIndex("CREATE INDEX ON %s(v2) USING 'sai'"); - - numRows = nextInt(50000, 200000); - } - - @Test - public void randomIntersectionTest() throws Throwable - { - if (partitionRestricted) - runRestrictedQueries(); - else - runUnrestrictedQueries(); - } - - private void runRestrictedQueries() throws Throwable - { - Map<Integer, List<TestRow>> testRowMap = buildAndLoadTestRows(); - - beforeAndAfterFlush(() -> { - for (int queryCount = 0; queryCount < nextInt(10, 100); queryCount++) - { - int pk = testRowMap.keySet().stream().skip(nextInt(0, testRowMap.size())).findFirst().orElseThrow(); - int v1 = nextV1(); - int v2 = nextV2(); - - List<Object[]> expected = testRowMap.get(pk) - .stream() - .sorted(Comparator.comparingInt(o -> o.ck)) - .filter(row -> row.v1 > v1 && row.v2 > v2) - .map(row -> row(row.ck)) - .collect(Collectors.toList()); - - assertRows(execute("SELECT ck FROM %s WHERE pk = ? AND v1 > ? AND v2 > ?", pk, v1, v2), expected.toArray(EMPTY_ROWS)); - } - }); - } - - private void runUnrestrictedQueries() throws Throwable - { - Map<Integer, List<TestRow>> testRowMap = buildAndLoadTestRows(); - - beforeAndAfterFlush(() -> { - for (int queryCount = 0; queryCount < nextInt(10, 100); queryCount++) - { - int v1 = nextV1(); - int v2 = nextV2(); - - List<Object[]> expected = testRowMap.values() - .stream() - .flatMap(Collection::stream) - .filter(row -> row.v1 == v1 && row.v2 == v2) - .map(row -> row(row.ck)) - .collect(Collectors.toList()); - - assertRowsIgnoringOrder(execute("SELECT ck FROM %s WHERE v1 = ? AND v2 = ?", v1, v2), expected.toArray(EMPTY_ROWS)); - } - }); - } - - private Map<Integer, List<TestRow>> buildAndLoadTestRows() - { - Map<Integer, List<TestRow>> testRowMap = new HashMap<>(); - - int clusterSize = largePartition ? nextInt(500, 5000) : nextInt(10, 100); - int partition = nextInt(0, numRows); - List<TestRow> rowList = new ArrayList<>(clusterSize); - testRowMap.put(partition, rowList); - int clusterCount = 0; - for (int index = 0; index < numRows; index++) - { - TestRow row = new TestRow(partition, nextInt(10, numRows), nextV1(), nextV2()); - while (rowList.contains(row)) - row = new TestRow(partition, nextInt(10, numRows), nextV1(), nextV2()); - - rowList.add(row); - clusterCount++; - if (clusterCount == clusterSize) - { - clusterCount = 0; - clusterSize = largePartition ? nextInt(500, 5000) : nextInt(10, 100); - partition = nextInt(0, numRows); - while (testRowMap.containsKey(partition)) - partition = nextInt(0, numRows); - rowList = new ArrayList<>(clusterSize); - testRowMap.put(partition, rowList); - } - } - testRowMap.values().stream().flatMap(Collection::stream).forEach(row -> execute("INSERT INTO %s (pk, ck, v1, v2) VALUES (?, ?, ?, ?)", - row.pk, row.ck, row.v1, row.v2)); - return testRowMap; - } - - private int nextV1() - { - return v1Cardinality ? nextInt(10, numRows/10) : nextInt(10, numRows/1000); - } - - private int nextV2() - { - return v2Cardinality ? nextInt(10, numRows/10) : nextInt(10, numRows/1000); - } - - private static class TestRow implements Comparable<TestRow> - { - final int pk; - final int ck; - final int v1; - final int v2; - - TestRow(int pk, int ck, int v1, int v2) - { - this.pk = pk; - this.ck = ck; - this.v1 = v1; - this.v2 = v2; - } - - @Override - public int compareTo(TestRow other) - { - int cmp = Integer.compare(pk, other.pk); - if (cmp != 0) - return cmp; - return Integer.compare(ck, other.ck); - } - - @Override - public boolean equals(Object obj) - { - if (obj instanceof TestRow) - return compareTo((TestRow) obj) == 0; - - return false; - } - - @Override - public int hashCode() - { - return Objects.hash(pk, ck); - } - } -} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/StaticColumnIndexTest.java b/test/unit/org/apache/cassandra/index/sai/cql/StaticColumnIndexTest.java index 0bdee106fd..faaf74f5c6 100644 --- a/test/unit/org/apache/cassandra/index/sai/cql/StaticColumnIndexTest.java +++ b/test/unit/org/apache/cassandra/index/sai/cql/StaticColumnIndexTest.java @@ -49,7 +49,26 @@ public class StaticColumnIndexTest extends SAITester execute("INSERT INTO %s(pk, ck, val2) VALUES(?, ?, ?)", 1, 2, 2000); execute("INSERT INTO %s(pk, ck, val1, val2) VALUES(?, ?, ?, ?)", 2, 1, 40, 2000); - beforeAndAfterFlush(() -> assertRows(execute("SELECT pk, ck, val1, val2 FROM %s WHERE val1 = 20 AND val2 = 2000 ALLOW FILTERING"), + beforeAndAfterFlush(() -> assertRows(execute("SELECT pk, ck, val1, val2 FROM %s WHERE val1 = 20 AND val2 = 2000"), row(1, 2, 20, 2000))); } + + @Test + public void staticAndNonStaticRangeIntersection() throws Throwable + { + createTable("CREATE TABLE %s (pk int, ck int, v1 int, s1 int static, PRIMARY KEY(pk, ck))"); + createIndex("CREATE INDEX ON %s(v1) USING 'sai'"); + createIndex("CREATE INDEX ON %s(s1) USING 'sai'"); + + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 1, 0); + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 2, 1); + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 3, 2); + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 4, 3); + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 5, 4); + execute("INSERT INTO %s (pk, ck, v1) VALUES (?, ?, ?)", 0, 6, 5); + + execute("INSERT INTO %s (pk, s1) VALUES (?, ?)", 0, 100); + + beforeAndAfterFlush(() -> assertRowCount(execute("SELECT * FROM %s WHERE pk = ? AND v1 > ? AND s1 = ?", 0, 2, 100), 3)); + } } diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomIntersectionTester.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomIntersectionTester.java new file mode 100644 index 0000000000..3b1436ae39 --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomIntersectionTester.java @@ -0,0 +1,270 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.junit.Before; +import org.junit.runners.Parameterized; + +import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.index.sai.utils.SAIRandomizedTester; + +public abstract class RandomIntersectionTester extends SAIRandomizedTester +{ + private static final Object[][] EMPTY_ROWS = new Object[][]{}; + + protected enum Mode { REGULAR, STATIC, REGULAR_STATIC, TWO_REGULAR_ONE_STATIC } + + @Parameterized.Parameter + public String testName; + + @Parameterized.Parameter(1) + public boolean partitionRestricted; + + @Parameterized.Parameter(2) + public boolean largePartition; + + @Parameterized.Parameter(3) + public boolean v1Cardinality; + + @Parameterized.Parameter(4) + public boolean v2Cardinality; + + @Parameterized.Parameter(5) + public Mode mode; + + private int numPartitions; + + @Before + public void createTableAndIndexes() + { + CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.setInt(3); + + createTable("CREATE TABLE %s (pk int, ck int, v1 int, v2 int, s1 int static, s2 int static, PRIMARY KEY(pk, ck))"); + createIndex("CREATE INDEX ON %s(v1) USING 'sai'"); + createIndex("CREATE INDEX ON %s(v2) USING 'sai'"); + createIndex("CREATE INDEX ON %s(s1) USING 'sai'"); + createIndex("CREATE INDEX ON %s(s2) USING 'sai'"); + + numPartitions = nextInt(15000, 100000); + } + + protected void runRestrictedQueries() throws Throwable + { + Map<Integer, List<TestRow>> testRowMap = buildAndLoadTestRows(); + + beforeAndAfterFlush(() -> { + for (int queryCount = 0; queryCount < nextInt(10, 100); queryCount++) + { + int pk = testRowMap.keySet().stream().skip(nextInt(0, testRowMap.size())).findFirst().orElseThrow(); + int v1 = nextV1(); + int v2 = nextV2(); + + Predicate<TestRow> predicate = null; + + if (mode == Mode.REGULAR) + predicate = row -> row.v1 > v1 && row.v2 > v2; + else if (mode == Mode.STATIC) + predicate = row -> row.s1 > v1 && row.s2 > v2; + else if (mode == Mode.REGULAR_STATIC) + predicate = row -> row.v1 > v1 && row.s2 > v2; + else if (mode == Mode.TWO_REGULAR_ONE_STATIC) + predicate = row -> row.v1 > v1 && row.v2 > v2 && row.s2 > v2; + + assert predicate != null : "Predicate should be assigned!"; + + List<Object[]> expected = testRowMap.get(pk) + .stream() + .sorted(Comparator.comparingInt(o -> o.ck)) + .filter(predicate) + .map(row -> row(row.pk, row.ck)) + .collect(Collectors.toList()); + + UntypedResultSet result = null; + + if (mode == Mode.REGULAR) + result = execute("SELECT pk, ck FROM %s WHERE pk = ? AND v1 > ? AND v2 > ?", pk, v1, v2); + else if (mode == Mode.STATIC) + result = execute("SELECT pk, ck FROM %s WHERE pk = ? AND s1 > ? AND s2 > ?", pk, v1, v2); + else if (mode == Mode.REGULAR_STATIC) + result = execute("SELECT pk, ck FROM %s WHERE pk = ? AND v1 > ? AND s2 > ?", pk, v1, v2); + else if (mode == Mode.TWO_REGULAR_ONE_STATIC) + result = execute("SELECT pk, ck FROM %s WHERE pk = ? AND v1 > ? AND v2 > ? AND s2 > ?", pk, v1, v2, v2); + + assertRows(result, expected.toArray(EMPTY_ROWS)); + } + }); + } + + protected void runUnrestrictedQueries() throws Throwable + { + Map<Integer, List<TestRow>> testRowMap = buildAndLoadTestRows(); + + beforeAndAfterFlush(() -> { + for (int queryCount = 0; queryCount < nextInt(10, 100); queryCount++) + { + int v1 = nextV1(); + int v2 = nextV2(); + + Predicate<TestRow> predicate = null; + + if (mode == Mode.REGULAR) + predicate = row -> row.v1 == v1 && row.v2 > v2; + else if (mode == Mode.STATIC) + predicate = row -> row.s1 > v1 && row.s2 > v2; + else if (mode == Mode.REGULAR_STATIC) + predicate = row -> row.v1 == v1 && row.s2 > v2; + else if (mode == Mode.TWO_REGULAR_ONE_STATIC) + predicate = row -> row.v1 == v1 && row.v2 > v2 && row.s2 > v2; + + assert predicate != null : "Predicate should be assigned!"; + + List<Object[]> expected = testRowMap.values() + .stream() + .flatMap(Collection::stream) + .filter(predicate) + .map(row -> row(row.pk, row.ck)) + .collect(Collectors.toList()); + + UntypedResultSet result = null; + + if (mode == Mode.REGULAR) + result = execute("SELECT pk, ck FROM %s WHERE v1 = ? AND v2 > ?", v1, v2); + else if (mode == Mode.STATIC) + result = execute("SELECT pk, ck FROM %s WHERE s1 > ? AND s2 > ?", v1, v2); + else if (mode == Mode.REGULAR_STATIC) + result = execute("SELECT pk, ck FROM %s WHERE v1 = ? AND s2 > ?", v1, v2); + else if (mode == Mode.TWO_REGULAR_ONE_STATIC) + result = execute("SELECT pk, ck FROM %s WHERE v1 = ? AND v2 > ? AND s2 > ?", v1, v2, v2); + + assertRowsIgnoringOrder(result, expected.toArray(EMPTY_ROWS)); + } + }); + } + + private Map<Integer, List<TestRow>> buildAndLoadTestRows() + { + Map<Integer, List<TestRow>> testRowMap = new HashMap<>(); + + int clusterSize = nextPartitionSize(); + int partition = nextInt(0, numPartitions); + int s1 = nextV1(); + int s2 = nextV2(); + List<TestRow> rowList = new ArrayList<>(clusterSize); + testRowMap.put(partition, rowList); + int clusterCount = 0; + + for (int index = 0; index < numPartitions; index++) + { + TestRow row = new TestRow(partition, nextInt(10, numPartitions), nextV1(), nextV2(), s1, s2); + while (rowList.contains(row)) + row = new TestRow(partition, nextInt(10, numPartitions), nextV1(), nextV2(), s1, s2); + + rowList.add(row); + clusterCount++; + + if (clusterCount == clusterSize) + { + clusterCount = 0; + clusterSize = nextPartitionSize(); + partition = nextInt(0, numPartitions); + while (testRowMap.containsKey(partition)) + partition = nextInt(0, numPartitions); + rowList = new ArrayList<>(clusterSize); + testRowMap.put(partition, rowList); + } + } + + testRowMap.values().stream().flatMap(Collection::stream).forEach(row -> { + execute("INSERT INTO %s (pk, ck, v1, v2) VALUES (?, ?, ?, ?)", row.pk, row.ck, row.v1, row.v2); + execute("INSERT INTO %s (pk, s1, s2) VALUES (?, ?, ?)", row.pk, row.s1, row.s2); + }); + + return testRowMap; + } + + private int nextPartitionSize() + { + return largePartition ? nextInt(1024, 4096) : nextInt(1, 64); + } + + private int nextV1() + { + return v1Cardinality ? nextInt(10, numPartitions / 10) : nextInt(10, numPartitions / 1000); + } + + private int nextV2() + { + return v2Cardinality ? nextInt(10, numPartitions / 10) : nextInt(10, numPartitions / 1000); + } + + private static class TestRow implements Comparable<TestRow> + { + final int pk; + final int ck; + final int v1; + final int v2; + final int s1; + final int s2; + + TestRow(int pk, int ck, int v1, int v2, int s1, int s2) + { + this.pk = pk; + this.ck = ck; + this.v1 = v1; + this.v2 = v2; + this.s1 = s1; + this.s2 = s2; + } + + @Override + public int compareTo(TestRow other) + { + int cmp = Integer.compare(pk, other.pk); + if (cmp != 0) + return cmp; + return Integer.compare(ck, other.ck); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof TestRow) + return compareTo((TestRow) obj) == 0; + + return false; + } + + @Override + public int hashCode() + { + return Objects.hash(pk, ck); + } + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedIntersectionTest.java new file mode 100644 index 0000000000..4260ab96a8 --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.REGULAR_STATIC; + +@RunWith(Parameterized.class) +public class RandomMixedIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition unrestricted, high, high", false, true, true, true, REGULAR_STATIC}); + parameters.add(new Object[] { "Large partition unrestricted, low, low", false, true, false, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Large partition unrestricted, high, low", false, true, true, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, high, high", false, false, true, true, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, low, low", false, false, false, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, high, low", false, false, true, false, REGULAR_STATIC}); + + return parameters; + } + + @Test + public void testMixedIntersection() throws Throwable + { + runUnrestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedPartitionIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedPartitionIntersectionTest.java new file mode 100644 index 0000000000..dc12e927fa --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomMixedPartitionIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.REGULAR_STATIC; + +@RunWith(Parameterized.class) +public class RandomMixedPartitionIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition restricted, high, high", true, true, true, true, REGULAR_STATIC}); + parameters.add(new Object[] { "Large partition restricted, low, low", true, true, false, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Large partition restricted, high, low", true, true, true, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition restricted, high, high", true, false, true, true, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition restricted, low, low", true, false, false, false, REGULAR_STATIC}); + parameters.add(new Object[] { "Small partition restricted, high, low", true, false, true, false, REGULAR_STATIC}); + + return parameters; + } + + @Test + public void testMixedIntersection() throws Throwable + { + runRestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularIntersectionTest.java new file mode 100644 index 0000000000..f05e301103 --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.REGULAR; + +@RunWith(Parameterized.class) +public class RandomRegularIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition unrestricted, high, high", false, true, true, true, REGULAR }); + parameters.add(new Object[] { "Large partition unrestricted, low, low", false, true, false, false, REGULAR }); + parameters.add(new Object[] { "Large partition unrestricted, high, low", false, true, true, false, REGULAR }); + parameters.add(new Object[] { "Small partition unrestricted, high, high", false, false, true, true, REGULAR }); + parameters.add(new Object[] { "Small partition unrestricted, low, low", false, false, false, false, REGULAR }); + parameters.add(new Object[] { "Small partition unrestricted, high, low", false, false, true, false, REGULAR }); + + return parameters; + } + + @Test + public void testRegularIntersection() throws Throwable + { + runUnrestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularPartitionIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularPartitionIntersectionTest.java new file mode 100644 index 0000000000..b14fefe743 --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomRegularPartitionIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.REGULAR; + +@RunWith(Parameterized.class) +public class RandomRegularPartitionIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition restricted, high, high", true, true, true, true, REGULAR }); + parameters.add(new Object[] { "Large partition restricted, low, low", true, true, false, false, REGULAR }); + parameters.add(new Object[] { "Large partition restricted, high, low", true, true, true, false, REGULAR }); + parameters.add(new Object[] { "Small partition restricted, high, high", true, false, true, true, REGULAR }); + parameters.add(new Object[] { "Small partition restricted, low, low", true, false, false, false, REGULAR }); + parameters.add(new Object[] { "Small partition restricted, high, low", true, false, true, false, REGULAR }); + + return parameters; + } + + @Test + public void testRegularIntersection() throws Throwable + { + runRestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticIntersectionTest.java new file mode 100644 index 0000000000..a1c31eaddc --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.STATIC; + +@RunWith(Parameterized.class) +public class RandomStaticIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition unrestricted, high, high", false, true, true, true, STATIC }); + parameters.add(new Object[] { "Large partition unrestricted, low, low", false, true, false, false, STATIC }); + parameters.add(new Object[] { "Large partition unrestricted, high, low", false, true, true, false, STATIC }); + parameters.add(new Object[] { "Small partition unrestricted, high, high", false, false, true, true, STATIC }); + parameters.add(new Object[] { "Small partition unrestricted, low, low", false, false, false, false, STATIC }); + parameters.add(new Object[] { "Small partition unrestricted, high, low", false, false, true, false, STATIC }); + + return parameters; + } + + @Test + public void testStaticIntersection() throws Throwable + { + runUnrestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticPartitionIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticPartitionIntersectionTest.java new file mode 100644 index 0000000000..b5e458ecfb --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/RandomStaticPartitionIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.STATIC; + +@RunWith(Parameterized.class) +public class RandomStaticPartitionIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition restricted, high, high", true, true, true, true, STATIC }); + parameters.add(new Object[] { "Large partition restricted, low, low", true, true, false, false, STATIC }); + parameters.add(new Object[] { "Large partition restricted, high, low", true, true, true, false, STATIC }); + parameters.add(new Object[] { "Small partition restricted, high, high", true, false, true, true, STATIC }); + parameters.add(new Object[] { "Small partition restricted, low, low", true, false, false, false, STATIC }); + parameters.add(new Object[] { "Small partition restricted, high, low", true, false, true, false, STATIC }); + + return parameters; + } + + @Test + public void testStaticIntersection() throws Throwable + { + runRestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticIntersectionTest.java new file mode 100644 index 0000000000..ddf4551e5e --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.LinkedList; +import java.util.List; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.TWO_REGULAR_ONE_STATIC; + +@RunWith(Parameterized.class) +public class TwoRegularOneStaticIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition unrestricted, high, high", false, true, true, true, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Large partition unrestricted, low, low", false, true, false, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Large partition unrestricted, high, low", false, true, true, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, high, high", false, false, true, true, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, low, low", false, false, false, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition unrestricted, high, low", false, false, true, false, TWO_REGULAR_ONE_STATIC}); + + return parameters; + } + + @Test + public void testMixedIntersection() throws Throwable + { + runUnrestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticPartitionIntersectionTest.java b/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticPartitionIntersectionTest.java new file mode 100644 index 0000000000..2bfc7438a3 --- /dev/null +++ b/test/unit/org/apache/cassandra/index/sai/cql/intersection/TwoRegularOneStaticPartitionIntersectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.index.sai.cql.intersection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.LinkedList; +import java.util.List; + +import static org.apache.cassandra.index.sai.cql.intersection.RandomIntersectionTester.Mode.TWO_REGULAR_ONE_STATIC; + +@RunWith(Parameterized.class) +public class TwoRegularOneStaticPartitionIntersectionTest extends RandomIntersectionTester +{ + @Parameterized.Parameters(name = "{0}") + public static List<Object[]> parameters() + { + List<Object[]> parameters = new LinkedList<>(); + + parameters.add(new Object[] { "Large partition restricted, high, high", true, true, true, true, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Large partition restricted, low, low", true, true, false, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Large partition restricted, high, low", true, true, true, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition restricted, high, high", true, false, true, true, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition restricted, low, low", true, false, false, false, TWO_REGULAR_ONE_STATIC}); + parameters.add(new Object[] { "Small partition restricted, high, low", true, false, true, false, TWO_REGULAR_ONE_STATIC}); + + return parameters; + } + + @Test + public void testMixedIntersection() throws Throwable + { + runRestrictedQueries(); + } +} diff --git a/test/unit/org/apache/cassandra/index/sai/utils/PrimaryKeyTest.java b/test/unit/org/apache/cassandra/index/sai/utils/PrimaryKeyTest.java index 1c4b7d40ba..51edc67389 100644 --- a/test/unit/org/apache/cassandra/index/sai/utils/PrimaryKeyTest.java +++ b/test/unit/org/apache/cassandra/index/sai/utils/PrimaryKeyTest.java @@ -83,8 +83,7 @@ public class PrimaryKeyTest extends AbstractPrimaryKeyTester public void simplePartitonStaticAndSingleClusteringAscTest() { PrimaryKey.Factory factory = new PrimaryKey.Factory(Murmur3Partitioner.instance, simplePartitionStaticAndSingleClusteringAsc.comparator); -// int rows = nextInt(10, 100); - int rows = 10; + int rows = nextInt(10, 100); PrimaryKey[] keys = new PrimaryKey[rows]; int partition = 0; int clustering = 0; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org