BETWEEN where token(Y) > token(Z) returns wrong answer

patch by Arvind Kandpal; reviewed by Caleb Rackliffe, David Capwell for CASSANDRA-20154
This commit is contained in:
Arvind Kandpal 2026-01-20 12:31:36 -08:00 committed by David Capwell
parent 3885395752
commit bad0a10343
15 changed files with 214 additions and 142 deletions

View File

@ -1,4 +1,5 @@
5.1
* BETWEEN where token(Y) > token(Z) returns wrong answer (CASSANDRA-20154)
* Optimize memtable flush logic (CASSANDRA-21083)
* No need to evict already prepared statements, as it creates a race condition between multiple threads (CASSANDRA-17401)
* Include Level information for UnifiedCompactionStrategy in nodetool tablestats output (CASSANDRA-20820)

View File

@ -788,8 +788,8 @@ public enum Operator
public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
{
List<ByteBuffer> buffers = ListType.getInstance(type, false).unpack(rightOperand);
// We use compare instead of compareForCQL to deal properly with reversed clustering columns
return type.compare(leftOperand, buffers.get(0)) >= 0 && type.compare(leftOperand, buffers.get(1)) <= 0;
AbstractType<?> unwrapped = type.unwrap();
return unwrapped.compare(leftOperand, buffers.get(0)) >= 0 && unwrapped.compare(leftOperand, buffers.get(1)) <= 0;
}
@Override
@ -802,11 +802,6 @@ public enum Operator
public void restrict(RangeSet<ClusteringElements> rangeSet, List<ClusteringElements> args, IPartitioner partitioner)
{
assert args.size() == 2 : this + " accepts exactly two values";
// avoid sorting when working with token restrictions, otherwise we can't know the difference between these queries:
// select * from x.y where token(id) between 0 and MIN_TOKEN
// select * from x.y where token(id) between MIN_TOKEN and 0
if (!args.get(0).token)
args.sort(ClusteringElements.CQL_COMPARATOR);
rangeSet.removeAll(ClusteringElements.lessThan(args.get(0), partitioner));
rangeSet.removeAll(ClusteringElements.greaterThan(args.get(1), partitioner));
}

View File

@ -343,8 +343,7 @@ public final class SimpleRestriction implements SingleRestriction
List<ByteBuffer> buffers = bindAndGet(options);
if (operator.kind() != Operator.Kind.BINARY)
{
// For BETWEEN we support like in SQL reversed bounds
if (operator.kind() == Operator.Kind.TERNARY)
if (operator == Operator.IN && !column.type.isCounter())
buffers.sort(column.type);
filter.add(column, operator, multiInputOperatorValues(column, buffers));
}

View File

@ -246,8 +246,7 @@ public abstract class Expression
Value first = new Value(buffers.get(0), indexTermType);
Value second = new Value(buffers.get(1), indexTermType);
// SimpleRestriction#addToRowFilter() ensures correct bounds ordering, but SAI enforces a non-arbitrary
// ordering between IPv4 and IPv6 addresses, so correction may still be necessary.
// SAI enforces a non-arbitrary ordering between IPv4 and IPv6 addresses, so correction may still be necessary.
boolean outOfOrder = indexTermType.compare(first.encoded, second.encoded) > 0;
lower = new Bound(outOfOrder ? second : first, true);
upper = new Bound(outOfOrder ? first : second, true);

View File

@ -0,0 +1,54 @@
/*
* 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.distributed.test;
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import static org.junit.Assert.assertEquals;
/**
* Tests that the BETWEEN operator respects SQL semantics:
* - Normal BETWEEN (low <= high) returns rows.
* - Inverted BETWEEN (low > high) returns no rows (empty result).
*/
public class BetweenInversionTest extends TestBaseImpl
{
@Test
public void testBetweenInversion() throws Throwable
{
try (Cluster cluster = init(Cluster.build(1).start()))
{
cluster.schemaChange("CREATE KEYSPACE ks WITH replication = {'class':'SimpleStrategy','replication_factor':1}");
cluster.schemaChange("CREATE TABLE ks.t1 (pk int PRIMARY KEY, val text)");
cluster.coordinator(1).execute("INSERT INTO ks.t1 (pk,val) VALUES (1,'a')", ConsistencyLevel.ALL);
cluster.coordinator(1).execute("INSERT INTO ks.t1 (pk,val) VALUES (2,'b')", ConsistencyLevel.ALL);
Object[][] rows = cluster.coordinator(1)
.execute("SELECT * FROM ks.t1 WHERE pk BETWEEN 1 AND 2 ALLOW FILTERING", ConsistencyLevel.ALL);
assertEquals(2, rows.length);
Object[][] inverted = cluster.coordinator(1)
.execute("SELECT * FROM ks.t1 WHERE pk BETWEEN 2 AND 1 ALLOW FILTERING", ConsistencyLevel.ALL);
assertEquals(0, inverted.length);
}
}
}

View File

@ -349,6 +349,86 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
return state.command(rs, select, symbol.detailedName() + (indexed == null ? "" : ", indexed with " + indexed.indexDDL.indexer.name()));
}
public Property.Command<State, Void, ?> clusteringBetweenQuery(RandomSource rs, State state)
{
ImmutableUniqueList<Symbol> cks = state.model.factory.clusteringColumns;
if (cks.isEmpty())
return Property.ignoreCommand();
// Select a single partition
BytesPartitionState.Ref ref = rs.pickOrderedSet(state.model.partitionKeys());
BytesPartitionState partition = state.model.get(ref);
NavigableSet<Clustering<ByteBuffer>> clusteringKeys = partition.clusteringKeys();
// Use first clustering column for BETWEEN
Symbol ckSymbol = cks.get(0);
ByteBuffer low, high;
boolean useRealValues = clusteringKeys.size() >= 2 && rs.nextBoolean();
if (useRealValues)
{
// low and high can match, have high < low, and low < high... this is all expected
// between where low > high should return nothing
// between where low == high should be the same as a ck=? query
low = rs.pickOrderedSet(clusteringKeys).bufferAt(0);
high = rs.pickOrderedSet(clusteringKeys).bufferAt(0);
}
else
{
// Generate random values
Gen<ByteBuffer> bytesGen = toGen(getTypeSupport(ckSymbol.type()).bytesGen());
low = bytesGen.next(rs);
high = bytesGen.next(rs);
}
Select.Builder builder = Select.builder().table(state.metadata);
// Add partition key restrictions
ImmutableUniqueList<Symbol> pks = state.model.factory.partitionColumns;
Clustering<ByteBuffer> pkKey = ref.key;
for (Symbol pk : pks)
builder.value(pk, pkKey.bufferAt(pks.indexOf(pk)));
String annotation;
if (rs.nextBoolean())
{
builder.between(ckSymbol, state.value(rs, low, ckSymbol.type()), state.value(rs, high, ckSymbol.type()));
annotation = "clustering BETWEEN";
}
else if (rs.nextBoolean())
{
builder.where(ckSymbol, Inequality.GREATER_THAN_EQ, low);
builder.where(ckSymbol, Inequality.LESS_THAN_EQ, high);
annotation = "clustering >= AND <=";
}
else
{
builder.where(ckSymbol, Inequality.GREATER_THAN, low);
builder.where(ckSymbol, Inequality.LESS_THAN, high);
annotation = "clustering > AND <";
}
if (rs.nextBoolean())
builder.orderByColumn(ckSymbol, rs.pick(Select.OrderBy.Ordering.values()));
// Check if clustering column is indexed
var indexed = state.indexes.get(ckSymbol);
if (indexed != null)
annotation += " (indexed with " + indexed.indexDDL.indexer.name() + ')';
// Check if column is reversed (DESC)
if (ckSymbol.reversed)
annotation += " [DESC]";
if (!useRealValues)
annotation += " [random values]";
Select select = builder.build();
return state.command(rs, select, annotation);
}
protected State createState(RandomSource rs, Cluster cluster)
{
return new State(rs, cluster);
@ -383,6 +463,7 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
.addIf(State::allowNonPartitionQuery, this::nonPartitionQuery)
.addIf(State::allowNonPartitionMultiColumnQuery, this::multiColumnQuery)
.addIf(State::allowPartitionQuery, this::partitionRestrictedQuery)
.addIf(State::allowClusteringBetweenQuery, this::clusteringBetweenQuery)
.destroyState(State::close)
.commandsTransformer(LoggingCommand.factory())
.onSuccess(onSuccess(logger))
@ -608,6 +689,11 @@ public class SingleNodeTableWalkTest extends StatefulASTBase
return !(model.isEmpty() || searchableNonPartitionColumns.isEmpty());
}
public boolean allowClusteringBetweenQuery()
{
return hasPartitions() && !model.factory.clusteringColumns.isEmpty();
}
@Override
public String toString()
{

View File

@ -46,7 +46,6 @@ import accord.utils.Property;
import accord.utils.RandomSource;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.KnownIssue;
import org.apache.cassandra.cql3.ast.Conditional.Where.Inequality;
import org.apache.cassandra.cql3.ast.FunctionCall;
import org.apache.cassandra.cql3.ast.Mutation;
@ -134,13 +133,6 @@ public class SingleNodeTokenConflictTest extends StatefulASTBase
ByteBuffer left = state.pkGen.next(rs);
ByteBuffer right = state.betweenEqGen.next(rs) ? left : state.pkGen.next(rs);
int rc = PK_TYPE.compare(left, right);
if (rc > 0 && IGNORED_ISSUES.contains(KnownIssue.BETWEEN_START_LARGER_THAN_END))
{
ByteBuffer tmp = left;
left = right;
right = tmp;
rc = PK_TYPE.compare(left, right);
}
Select select = Select.builder()
.table(state.tableRef)
.between(PK, state.pkValue(rs, left), state.pkValue(rs, right))
@ -205,16 +197,6 @@ public class SingleNodeTokenConflictTest extends StatefulASTBase
LongToken start = Murmur3Partitioner.instance.getToken(left);
LongToken end = Murmur3Partitioner.instance.getToken(right);
int rc = start.compareTo(end);
if (rc > 0 && IGNORED_ISSUES.contains(KnownIssue.BETWEEN_START_LARGER_THAN_END))
{
ByteBuffer tmp = left;
left = right;
right = tmp;
LongToken tmp2 = start;
start = end;
end = tmp2;
rc = start.compareTo(end);
}
Select select = Select.builder()
.table(state.tableRef)
.between(FunctionCall.tokenByColumns(PK),

View File

@ -371,13 +371,6 @@ public class StatefulASTBase extends TestBaseImpl
}
else
{
// it's possible that the range was flipped, which is known bug with BETWEEN, so
// make sure the range is not flipped until that bug is fixed
if (IGNORED_ISSUES.contains(KnownIssue.BETWEEN_START_LARGER_THAN_END))
{
min = Literal.of(key.token.getLongValue());
max = Literal.of(Long.MIN_VALUE);
}
select = Select.builder(state.metadata)
.between(tokenCall, min, max)
.build();

View File

@ -44,6 +44,7 @@ import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@ -1580,7 +1581,7 @@ public class ASTSingleTableModel
ImmutableUniqueList<Symbol> selectOrder = factory.selectionOrder;
ImmutableUniqueList<Symbol> targetOrder = columns(select);
if (select.where.isEmpty())
return SelectResult.ordered(targetOrder, filter(getRowsAsByteBuffer(applyLimits(all(), select.perPartitionLimit, select.limit)), selectOrder, targetOrder));
return SelectResult.ordered(targetOrder, filter(getRowsAsByteBuffer(applyLimits(all(), select.perPartitionLimit, select.limit, select.orderBy)), selectOrder, targetOrder));
LookupContext ctx = context(select);
List<PrimaryKey> primaryKeys;
if (ctx.unmatchable)
@ -1606,13 +1607,17 @@ public class ASTSingleTableModel
// partial tested (handles many columns, tests are single column)
primaryKeys = search(ctx);
}
primaryKeys = applyLimits(primaryKeys, select.perPartitionLimit, select.limit);
primaryKeys = applyLimits(primaryKeys, select.perPartitionLimit, select.limit, select.orderBy);
//TODO (correctness): now that we have the rows we need to handle the selections/aggregation/limit/group-by/etc.
return new SelectResult(targetOrder, filter(getRowsAsByteBuffer(primaryKeys), selectOrder, targetOrder), ctx.unordered);
}
private List<PrimaryKey> applyLimits(List<PrimaryKey> primaryKeys, Optional<Value> perPartitionLimitOpt, Optional<Value> limitOpt)
private List<PrimaryKey> applyLimits(List<PrimaryKey> primaryKeys,
Optional<Value> perPartitionLimitOpt, Optional<Value> limitOpt,
Optional<Select.OrderBy> orderBy)
{
if (orderBy.isPresent() && shouldReverse(orderBy.get()))
primaryKeys = Lists.reverse(primaryKeys);
if (perPartitionLimitOpt.isPresent())
{
int limit = Int32Type.instance.compose(eval(perPartitionLimitOpt.get()));
@ -1640,6 +1645,18 @@ public class ASTSingleTableModel
return primaryKeys;
}
private boolean shouldReverse(Select.OrderBy orderBy)
{
for (var block : orderBy.ordered)
{
Symbol col = (Symbol) block.expression; //TODO (coverage): do we support anything other than symbol?
col = factory.clusteringColumns.get(col); // switch to table symbol so we know if its reversed or not
if (col.reversed != (block.ordering == Select.OrderBy.Ordering.DESC))
return true;
}
return false;
}
private List<PrimaryKey> all()
{
List<PrimaryKey> primaryKeys = new ArrayList<>();

View File

@ -2698,13 +2698,13 @@ public abstract class CQLTester
return rows;
}
protected void assertEmpty(UntypedResultSet result) throws Throwable
protected void assertEmpty(UntypedResultSet result)
{
if (result != null && !result.isEmpty())
throw new AssertionError(String.format("Expected empty result but got %d rows: %s \n", result.size(), makeRowStrings(result)));
}
protected void assertInvalid(String query, Object... values) throws Throwable
protected void assertInvalid(String query, Object... values)
{
assertInvalidMessage(null, query, values);
}
@ -2778,7 +2778,7 @@ public abstract class CQLTester
: replaceValues(query, values);
}
protected void assertValidSyntax(String query) throws Throwable
protected void assertValidSyntax(String query)
{
try
{
@ -2791,12 +2791,12 @@ public abstract class CQLTester
}
}
protected void assertInvalidSyntax(String query, Object... values) throws Throwable
protected void assertInvalidSyntax(String query, Object... values)
{
assertInvalidSyntaxMessage(null, query, values);
}
protected void assertInvalidSyntaxMessage(String errorMessage, String query, Object... values) throws Throwable
protected void assertInvalidSyntaxMessage(String errorMessage, String query, Object... values)
{
try
{

View File

@ -27,8 +27,6 @@ import java.util.EnumSet;
*/
public enum KnownIssue
{
BETWEEN_START_LARGER_THAN_END("https://issues.apache.org/jira/browse/CASSANDRA-20154",
"BETWEEN is matching values when start > end, which should never return anything"),
SAI_INET_MIXED("https://issues.apache.org/jira/browse/CASSANDRA-19492",
"SAI converts ipv4 to ipv6 to simplify the index, this causes issues with range search as it starts to mix the values, which isn't always desirable or intuative"),
CUSTOM_INDEX_MAX_COLUMN_48("https://issues.apache.org/jira/browse/CASSANDRA-19897",

View File

@ -417,7 +417,12 @@ FROM [keyspace_name.] table_name
public T orderByColumn(String name, AbstractType<?> type, OrderBy.Ordering ordering)
{
orderBy.add(new Symbol(name, type), ordering);
return orderByColumn(new Symbol(name, type), ordering);
}
public T orderByColumn(Symbol column, OrderBy.Ordering ordering)
{
orderBy.add(column, ordering);
return (T) this;
}

View File

@ -31,7 +31,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
private static final ByteBuffer TOO_BIG = ByteBuffer.allocate(1024 * 65);
@Test
public void testSingleClusteringInvalidQueries() throws Throwable
public void testSingleClusteringInvalidQueries()
{
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
@ -53,7 +53,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultiClusteringInvalidQueries() throws Throwable
public void testMultiClusteringInvalidQueries()
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))");
@ -229,9 +229,8 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, 0, 0, 0),
row(0, 0, 1, 0));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) BETWEEN (?, ?, ?) AND (?, ?, ?)", 0, 0, 1, 0, 0, 0, 0),
row(0, 0, 0, 0),
row(0, 0, 1, 0));
// BETWEEN with inverted bounds returns empty result (first bound > second bound)
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) BETWEEN (?, ?, ?) AND (?, ?, ?)", 0, 0, 1, 0, 0, 0, 0));
}
@Test
@ -317,7 +316,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testSinglePartitionInvalidQueries() throws Throwable
public void testSinglePartitionInvalidQueries()
{
createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)");
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
@ -329,7 +328,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testSingleClustering() throws Throwable
public void testSingleClustering()
{
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
@ -481,7 +480,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultipleClustering() throws Throwable
public void testMultipleClustering()
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))");
@ -726,11 +725,8 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, 0, 0, 0)
);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) BETWEEN (?, ?) AND (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 0, 0),
row(0, 0, 1, 1),
row(0, 0, 1, 0),
row(0, 0, 0, 0)
);
// BETWEEN with inverted bounds returns empty result (first bound > second bound)
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b, c) BETWEEN (?, ?) AND (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 0, 0));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) < (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1),
row(0, 0, 1, 0),
@ -743,11 +739,8 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, 0, 0, 0)
);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) BETWEEN (?, ?, ?) AND (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1, 0, 0, 0),
row(0, 0, 1, 1),
row(0, 0, 1, 0),
row(0, 0, 0, 0)
);
// BETWEEN with inverted bounds returns empty result (first bound > second bound)
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) BETWEEN (?, ?, ?) AND (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1, 0, 0, 0));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b) < (?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 0, 1),
row(0, 0, 1, 1)
@ -952,7 +945,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultipleClusteringWithIndex() throws Throwable
public void testMultipleClusteringWithIndex()
{
Util.assumeLegacySecondaryIndex();
createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, PRIMARY KEY (a, b, c, d))");
@ -1042,7 +1035,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultipleClusteringWithIndexAndValueOver64K() throws Throwable
public void testMultipleClusteringWithIndexAndValueOver64K()
{
Util.assumeLegacySecondaryIndex();
createTable("CREATE TABLE %s (a int, b blob, c int, d int, PRIMARY KEY (a, b, c))");
@ -1056,7 +1049,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultiColumnRestrictionsWithIndex() throws Throwable
public void testMultiColumnRestrictionsWithIndex()
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, v int, PRIMARY KEY (a, b, c, d, e))");
createIndex("CREATE INDEX ON %s (v)");
@ -1085,7 +1078,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testMultiplePartitionKeyAndMultiClusteringWithIndex() throws Throwable
public void testMultiplePartitionKeyAndMultiClusteringWithIndex()
{
Util.assumeLegacySecondaryIndex();
createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))");
@ -1229,7 +1222,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testWithUnsetValues() throws Throwable
public void testWithUnsetValues()
{
createTable("CREATE TABLE %s (k int, i int, j int, s text, PRIMARY KEY(k,i,j))");
createIndex("CREATE INDEX s_index ON %s (s)");
@ -1330,32 +1323,12 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, -1, 0, -1, 0)
);
assertRows(execute(
// BETWEEN with inverted bounds returns empty result (first bound > second bound in logical order)
assertEmpty(execute(
"SELECT * FROM %s" +
" WHERE a = ? " +
"AND (b,c,d,e) BETWEEN (?,?,?,?) " +
"AND (?,?,?,?)", 0, 2, 0, 1, 1, -1, -1, -1, -1),
row(0, 2, 0, 1, 1),
row(0, 2, 0, -1, 0),
row(0, 2, 0, -1, 1),
row(0, 1, -1, 1, 0),
row(0, 1, -1, 1, 1),
row(0, 1, -1, 0, 0),
row(0, 1, 0, 1, -1),
row(0, 1, 0, 1, 1),
row(0, 1, 0, 0, -1),
row(0, 1, 0, 0, 0),
row(0, 1, 0, 0, 1),
row(0, 1, 0, -1, -1),
row(0, 1, 1, 0, -1),
row(0, 1, 1, 0, 0),
row(0, 1, 1, 0, 1),
row(0, 1, 1, -1, 0),
row(0, 0, 0, 0, 0),
row(0, -1, 0, 0, 0),
row(0, -1, 0, -1, 0)
);
"AND (?,?,?,?)", 0, 2, 0, 1, 1, -1, -1, -1, -1));
assertRows(execute(
"SELECT * FROM %s" +
@ -1665,17 +1638,8 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, -1, 0, -1, 0)
);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b,c,d,e) BETWEEN (?,?,?,?) AND (?,?,?,?)", 0, 1, 0, 0, 0, -10, -1, -4, -1),
row(0, 1, -1, 1, 0),
row(0, 1, -1, 1, 1),
row(0, 1, -1, 0, 0),
row(0, 1, 0, 0, -1),
row(0, 1, 0, 0, 0),
row(0, 1, 0, -1, -1),
row(0, 0, 0, 0, 0),
row(0, -1, 0, 0, 0),
row(0, -1, 0, -1, 0)
);
// BETWEEN with inverted bounds returns empty result (first bound > second bound in logical order)
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b,c,d,e) BETWEEN (?,?,?,?) AND (?,?,?,?)", 0, 1, 0, 0, 0, -10, -1, -4, -1));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b,c,d,e) > (?,?,?,?)", 0, 1, 0, 0, 0),
row(0, 2, 0, 1, 1),
@ -2032,34 +1996,12 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, 2, -3, 1, 1)
);
assertRows(execute(
// BETWEEN with inverted bounds returns empty result (first bound > second bound in logical order)
assertEmpty(execute(
"SELECT * FROM %s" +
" WHERE a = ? " +
"AND (b,c,d,e) BETWEEN (?,?,?,?) " +
"AND (?,?,?,?)", 0, 2, 0, 1, 1, -1, -10, -10, -10),
row(0, -1, 0, 0, 0),
row(0, -1, 0, -1, 0),
row(0, 0, 0, 0, 0),
row(0, 1, 1, 0, -1),
row(0, 1, 1, 0, 0),
row(0, 1, 1, 0, 1),
row(0, 1, 1, -1, 0),
row(0, 1, 0, 1, -1),
row(0, 1, 0, 1, 1),
row(0, 1, 0, 0, -1),
row(0, 1, 0, 0, 0),
row(0, 1, 0, 0, 1),
row(0, 1, 0, -1, -1),
row(0, 1, -1, 1, 0),
row(0, 1, -1, 1, 1),
row(0, 1, -1, 0, 0),
row(0, 2, 0, 1, 1),
row(0, 2, 0, -1, 0),
row(0, 2, 0, -1, 1),
row(0, 2, -1, 1, 1),
row(0, 2, -3, 1, 1)
);
"AND (?,?,?,?)", 0, 2, 0, 1, 1, -1, -10, -10, -10));
assertRows(execute(
"SELECT * FROM %s" +
@ -2257,7 +2199,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testInvalidColumnNames() throws Throwable
public void testInvalidColumnNames()
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c))");
assertInvalidMessage("Undefined column name e", "SELECT * FROM %s WHERE (b, e) = (0, 0)");
@ -2269,7 +2211,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testInRestrictionsWithAllowFiltering() throws Throwable
public void testInRestrictionsWithAllowFiltering()
{
createTable("CREATE TABLE %s (pk int, c1 text, c2 int, c3 int, v int, primary key(pk, c1, c2, c3))");
execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "0", 0, 1, 3);
@ -2291,7 +2233,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
}
@Test
public void testInRestrictionsWithIndex() throws Throwable
public void testInRestrictionsWithIndex()
{
createTable("CREATE TABLE %s (pk int, c1 text, c2 int, c3 int, v int, primary key(pk, c1, c2, c3))");
createIndex("CREATE INDEX ON %s (c3)");

View File

@ -586,11 +586,9 @@ public class SelectSingleColumnRelationTest extends CQLTester
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE a = ? AND c = ? AND d BETWEEN ? AND ? AND f = ?", 0, 1, 1, 5, 0);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND b = ? AND c = ? AND d BETWEEN ? AND ? AND f = ?", 0, 0, 1, 1, 0, 5),
row(0, 0, 1, 1, 1, 5));
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND b = ? AND c = ? AND d BETWEEN ? AND ? AND f = ?", 0, 0, 1, 1, 0, 5));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND c = ? AND d BETWEEN ? AND ? AND f = ? ALLOW FILTERING", 0, 1, 1, 0, 5),
row(0, 0, 1, 1, 1, 5));
assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND c = ? AND d BETWEEN ? AND ? AND f = ? ALLOW FILTERING", 0, 1, 1, 0, 5));
}
@Test
@ -1352,9 +1350,7 @@ public class SelectSingleColumnRelationTest extends CQLTester
row (0, 2, 2, "MA"),
row (0, 3, 3, "MA"));
assertRows(execute("SELECT * FROM %s WHERE c2 BETWEEN 3 AND 2 ALLOW FILTERING"),
row (0, 2, 2, "MA"),
row (0, 3, 3, "MA"));
assertEmpty(execute("SELECT * FROM %s WHERE c2 BETWEEN 3 AND 2 ALLOW FILTERING"));
});
createTable("CREATE TABLE %s(p int, c int, c2 int, abbreviation ascii, PRIMARY KEY (p, c, c2)) WITH CLUSTERING ORDER BY (c DESC, c2 DESC)");
@ -1369,9 +1365,7 @@ public class SelectSingleColumnRelationTest extends CQLTester
row(0, 3, 3, "MA"),
row(0, 2, 2, "MA"));
assertRows(execute("SELECT * FROM %s WHERE c2 BETWEEN 3 AND 2 ALLOW FILTERING"),
row(0, 3, 3, "MA"),
row(0, 2, 2, "MA"));
assertEmpty(execute("SELECT * FROM %s WHERE c2 BETWEEN 3 AND 2 ALLOW FILTERING"));
});
}
}

View File

@ -95,6 +95,13 @@ public class ImmutableUniqueList<T> extends AbstractList<T> implements UniqueLis
return values[index];
}
public T get(T value)
{
int idx = indexOf(value);
if (idx == -1) throw new IllegalArgumentException("Unable to find value " + value);
return get(idx);
}
@Override
public int indexOf(Object o)
{