Allow filtering on partition key columns for queries without secondary indexes

patch by ZhaoYang and Alex Petrov; reviewed by Benjamin Lerer for CASSANDRA-11031
This commit is contained in:
ZhaoYang 2016-09-12 11:22:25 +02:00 committed by Benjamin Lerer
parent 64f12ab2c8
commit 3f49c328f2
20 changed files with 1638 additions and 88 deletions

View File

@ -1,4 +1,5 @@
3.10
* Allow filtering on partition key columns for queries without secondary indexes (CASSANDRA-11031)
* Fix Cassandra Stress reporting thread model and precision (CASSANDRA-12585)
* Add JMH benchmarks.jar (CASSANDRA-12586)
* Add row offset support to SASI (CASSANDRA-11990)

View File

@ -18,6 +18,8 @@ using the provided 'sstableupgrade' tool.
New features
------------
- Filtering on partition key columns is now also supported for queries without
secondary indexes.
- A slow query log has been added: slow queries will be logged at DEBUG level.
For more details refer to CASSANDRA-12403 and slow_query_log_timeout_in_ms
in cassandra.yaml.

View File

@ -252,17 +252,6 @@ public final class SingleColumnRelation extends Relation
checkFalse(!columnDef.isPrimaryKeyColumn() && !canHaveOnlyOneValue(),
"IN predicates on non-primary-key columns (%s) is not yet supported", columnDef.name);
}
else if (isSlice())
{
// Non EQ relation is not supported without token(), even if we have a 2ndary index (since even those
// are ordered by partitioner).
// Note: In theory we could allow it for 2ndary index queries with ALLOW FILTERING, but that would
// probably require some special casing
// Note bis: This is also why we don't bother handling the 'tuple' notation of #4851 for keys. If we
// lift the limitation for 2ndary
// index with filtering, we'll need to handle it though.
checkFalse(columnDef.isPartitionKey(), "Only EQ and IN relation are supported on the partition key (unless you use the token() function)");
}
checkFalse(isContainsKey() && !(receiver.type instanceof MapType), "Cannot use CONTAINS KEY on non-map column %s", receiver.name);
checkFalse(isContains() && !(receiver.type.isCollection()), "Cannot use CONTAINS on non-collection column %s", receiver.name);

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.restrictions;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.statements.Bound;
@ -48,4 +49,20 @@ interface PartitionKeyRestrictions extends Restrictions
* @return <code>true</code> if the specified bound is inclusive, <code>false</code> otherwise
*/
public boolean isInclusive(Bound b);
/**
* checks if specified restrictions require filtering
*
* @param cfm column family metadata
* @return <code>true</code> if filtering is required, <code>false</code> otherwise
*/
public boolean needFiltering(CFMetaData cfm);
/**
* Checks if the partition key has unrestricted components.
*
* @param cfm column family metadata
* @return <code>true</code> if the partition key has unrestricted components, <code>false</code> otherwise.
*/
public boolean hasUnrestrictedPartitionKeyComponents(CFMetaData cfm);
}

View File

@ -129,4 +129,30 @@ final class PartitionKeySingleRestrictionSet extends RestrictionSetWrapper imple
restriction.addRowFilterTo(filter, indexManager, options);
}
}
@Override
public boolean needFiltering(CFMetaData cfm)
{
if (isEmpty())
return false;
// slice or has unrestricted key component
return hasUnrestrictedPartitionKeyComponents(cfm) || hasSlice();
}
@Override
public boolean hasUnrestrictedPartitionKeyComponents(CFMetaData cfm)
{
return size() < cfm.partitionKeyColumns().size();
}
@Override
public boolean hasSlice()
{
for (SingleRestriction restriction : restrictions)
{
if (restriction.isSlice())
return true;
}
return false;
}
}

View File

@ -266,6 +266,16 @@ final class RestrictionSet implements Restrictions, Iterable<SingleRestriction>
return false;
}
public final boolean hasSlice()
{
for (SingleRestriction restriction : this)
{
if (restriction.isSlice())
return true;
}
return false;
}
/**
* Checks if all of the underlying restrictions are EQ or IN restrictions.
*

View File

@ -89,6 +89,11 @@ class RestrictionSetWrapper implements Restrictions
return restrictions.hasIN();
}
public boolean hasSlice()
{
return restrictions.hasSlice();
}
public boolean hasOnlyEqualityRestrictions()
{
return restrictions.hasOnlyEqualityRestrictions();

View File

@ -54,6 +54,12 @@ public interface Restrictions extends Restriction
*/
public boolean hasIN();
/**
* Checks if any of the underlying restrictions is a slice.
* @return <code>true</code> if any of the underlying restrictions is a slice, <code>false</code> otherwise
*/
public boolean hasSlice();
/**
* Checks if all of the underlying restrictions are EQ or IN restrictions.
*

View File

@ -190,11 +190,11 @@ public final class StatementRestrictions
}
// At this point, the select statement if fully constructed, but we still have a few things to validate
processPartitionKeyRestrictions(hasQueriableIndex);
processPartitionKeyRestrictions(hasQueriableIndex, allowFiltering, forView);
// Some but not all of the partition key columns have been specified;
// hence we need turn these restrictions into a row filter.
if (usesSecondaryIndexing)
if (usesSecondaryIndexing || partitionKeyRestrictions.needFiltering(cfm))
filterRestrictions.add(partitionKeyRestrictions);
if (selectsOnlyStaticColumns && hasClusteringColumnsRestriction())
@ -385,36 +385,48 @@ public final class StatementRestrictions
return this.usesSecondaryIndexing;
}
private void processPartitionKeyRestrictions(boolean hasQueriableIndex)
private void processPartitionKeyRestrictions(boolean hasQueriableIndex, boolean allowFiltering, boolean forView)
{
if (!type.allowPartitionKeyRanges())
{
checkFalse(partitionKeyRestrictions.isOnToken(),
"The token function cannot be used in WHERE clauses for %s statements", type);
if (hasUnrestrictedPartitionKeyComponents())
if (partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(cfm))
throw invalidRequest("Some partition key parts are missing: %s",
Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
// slice query
checkFalse(partitionKeyRestrictions.hasSlice(),
"Only EQ and IN relation are supported on the partition key (unless you use the token() function)"
+ " for %s statements", type);
}
else
{
// If there is a queriable index, no special condition are required on the other restrictions.
// But we still need to know 2 things:
// - If we don't have a queriable index, is the query ok
// - Is it queriable without 2ndary index, which is always more efficient
// If a component of the partition key is restricted by a relation, all preceding
// components must have a EQ. Only the last partition key component can be in IN relation.
if (partitionKeyRestrictions.isOnToken())
isKeyRange = true;
// If there are no partition restrictions or there's only token restriction, we have to set a key range
if (partitionKeyRestrictions.isOnToken())
isKeyRange = true;
if (hasUnrestrictedPartitionKeyComponents())
if (partitionKeyRestrictions.isEmpty() && partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(cfm))
{
if (!partitionKeyRestrictions.isEmpty())
{
if (!hasQueriableIndex)
throw invalidRequest("Partition key parts: %s must be restricted as other parts are",
Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
}
isKeyRange = true;
usesSecondaryIndexing = hasQueriableIndex;
}
// If there is a queriable index, no special condition is required on the other restrictions.
// But we still need to know 2 things:
// - If we don't have a queriable index, is the query ok
// - Is it queriable without 2ndary index, which is always more efficient
// If a component of the partition key is restricted by a relation, all preceding
// components must have a EQ. Only the last partition key component can be in IN relation.
if (partitionKeyRestrictions.needFiltering(cfm))
{
if (!allowFiltering && !forView && !hasQueriableIndex
&& (partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(cfm) || partitionKeyRestrictions.hasSlice()))
throw new InvalidRequestException(REQUIRES_ALLOW_FILTERING_MESSAGE);
if (partitionKeyRestrictions.hasIN())
throw new InvalidRequestException("IN restrictions are not supported when the query involves filtering");
isKeyRange = true;
usesSecondaryIndexing = hasQueriableIndex;
@ -422,15 +434,6 @@ public final class StatementRestrictions
}
}
/**
* Checks if the partition key has some unrestricted components.
* @return <code>true</code> if the partition key has some unrestricted components, <code>false</code> otherwise.
*/
private boolean hasUnrestrictedPartitionKeyComponents()
{
return partitionKeyRestrictions.size() < cfm.partitionKeyColumns().size();
}
public boolean hasPartitionKeyRestrictions()
{
return !partitionKeyRestrictions.isEmpty();
@ -622,8 +625,8 @@ public final class StatementRestrictions
private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options)
{
// Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the
// first component of a composite partition key).
if (hasUnrestrictedPartitionKeyComponents())
// first component of a composite partition key) queries that filter on the partition key.
if (partitionKeyRestrictions.needFiltering(cfm))
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
// We deal with IN queries for keys in other places, so we know buildBound will return only one result
@ -804,7 +807,7 @@ public final class StatementRestrictions
public boolean hasAllPKColumnsRestrictedByEqualities()
{
return !isPartitionKeyRestrictionsOnToken()
&& !hasUnrestrictedPartitionKeyComponents()
&& !partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(cfm)
&& (partitionKeyRestrictions.hasOnlyEqualityRestrictions())
&& !hasUnrestrictedClusteringColumns()
&& (clusteringColumnsRestrictions.hasOnlyEqualityRestrictions());

View File

@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.functions.Function;
@ -135,8 +136,8 @@ final class TokenFilter implements PartitionKeyRestrictions
*/
private List<ByteBuffer> filter(List<ByteBuffer> values, QueryOptions options) throws InvalidRequestException
{
RangeSet<Token> rangeSet = tokenRestriction.isSlice() ? toRangeSet(tokenRestriction, options)
: toRangeSet(tokenRestriction.values(options));
RangeSet<Token> rangeSet = tokenRestriction.hasSlice() ? toRangeSet(tokenRestriction, options)
: toRangeSet(tokenRestriction.values(options));
return filterWithRangeSet(rangeSet, values);
}
@ -285,4 +286,22 @@ final class TokenFilter implements PartitionKeyRestrictions
{
return restrictions.size();
}
@Override
public boolean needFiltering(CFMetaData cfm)
{
return restrictions.needFiltering(cfm);
}
@Override
public boolean hasUnrestrictedPartitionKeyComponents(CFMetaData cfm)
{
return restrictions.hasUnrestrictedPartitionKeyComponents(cfm);
}
@Override
public boolean hasSlice()
{
return restrictions.hasSlice();
}
}

View File

@ -57,11 +57,6 @@ public abstract class TokenRestriction implements PartitionKeyRestrictions
this.metadata = metadata;
}
public boolean isSlice()
{
return false;
}
public boolean hasIN()
{
return false;
@ -84,6 +79,24 @@ public abstract class TokenRestriction implements PartitionKeyRestrictions
return true;
}
@Override
public boolean needFiltering(CFMetaData cfm)
{
return false;
}
@Override
public boolean hasSlice()
{
return false;
}
@Override
public boolean hasUnrestrictedPartitionKeyComponents(CFMetaData cfm)
{
return false;
}
@Override
public List<ColumnDefinition> getColumnDefs()
{
@ -225,7 +238,7 @@ public abstract class TokenRestriction implements PartitionKeyRestrictions
}
@Override
public boolean isSlice()
public boolean hasSlice()
{
return true;
}

View File

@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -274,9 +275,19 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
return iter;
final CFMetaData metadata = iter.metadata();
long numberOfStaticColumnExpressions = expressions.stream().filter(e -> e.column.isStatic()).count();
final boolean filterStaticColumns = numberOfStaticColumnExpressions != 0;
final boolean filterNonStaticColumns = (expressions.size() - numberOfStaticColumnExpressions) > 0;
List<Expression> partitionLevelExpressions = new ArrayList<>();
List<Expression> rowLevelExpressions = new ArrayList<>();
for (Expression e: expressions)
{
if (e.column.isStatic() || e.column.isPartitionKey())
partitionLevelExpressions.add(e);
else
rowLevelExpressions.add(e);
}
long numberOfRegularColumnExpressions = rowLevelExpressions.size();
final boolean filterNonStaticColumns = numberOfRegularColumnExpressions > 0;
class IsSatisfiedFilter extends Transformation<UnfilteredRowIterator>
{
@ -285,9 +296,10 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
{
pk = partition.partitionKey();
// The filter might be on static columns, so need to check static row first.
if (filterStaticColumns && applyToRow(partition.staticRow()) == null)
return null;
// Short-circuit all partitions that won't match based on static and partition keys
for (Expression e : partitionLevelExpressions)
if (!e.isSatisfiedBy(metadata, partition.partitionKey(), partition.staticRow()))
return null;
UnfilteredRowIterator iterator = Transformation.apply(partition, this);
return (filterNonStaticColumns && !iterator.hasNext()) ? null : iterator;
@ -299,9 +311,10 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
if (purged == null)
return null;
for (Expression e : expressions)
for (Expression e : rowLevelExpressions)
if (!e.isSatisfiedBy(metadata, pk, purged))
return null;
return row;
}
}
@ -669,9 +682,6 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
// TODO: we should try to merge both code someday.
assert value != null;
if (row.isStatic() != column.isStatic())
return true;
switch (operator)
{
case EQ:

View File

@ -148,13 +148,6 @@ public class ViewFilteringTest extends CQLTester
throw new RuntimeException("MV alter failed: " + goodStatements.get(i), e);
}
}
try
{
createView("mv_foo", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE a = 1 AND b IS NOT NULL AND c IS NOT NULL AND d is NOT NULL PRIMARY KEY ((a, b), c, d)");
Assert.fail("Partial partition key restriction should not be allowed");
}
catch (InvalidQueryException exc) {}
}
@Test
@ -265,6 +258,210 @@ public class ViewFilteringTest extends CQLTester
);
}
@Test
public void testPartitionKeyFilteringUnrestrictedPart() throws Throwable
{
List<String> mvPrimaryKeys = Arrays.asList("((a, b), c)", "((b, a), c)", "(a, b, c)", "(c, b, a)", "((c, a), b)");
for (int i = 0; i < mvPrimaryKeys.size(); i++)
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY ((a, b), c))");
execute("USE " + keyspace());
executeNet(protocolVersion, "USE " + keyspace());
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 1, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 1, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 1, 1, 0);
logger.info("Testing MV primary key: {}", mvPrimaryKeys.get(i));
// only accept rows where a = 1
String viewName= "mv_test" + i;
createView(viewName, "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE a = 1 AND b IS NOT NULL AND c IS NOT NULL PRIMARY KEY " + mvPrimaryKeys.get(i));
waitForView(keyspace(), viewName);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 0),
row(1, 1, 1, 0)
);
// insert new rows that do not match the filter
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 0, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 1, 0, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 0),
row(1, 1, 1, 0)
);
// insert new row that does match the filter
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 1, 2, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 0),
row(1, 1, 1, 0),
row(1, 1, 2, 0)
);
// update rows that don't match the filter
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 1, 0, 0, 0);
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 1, 0, 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 0),
row(1, 1, 1, 0),
row(1, 1, 2, 0)
);
// update a row that does match the filter
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 1, 1, 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 1),
row(1, 1, 1, 0),
row(1, 1, 2, 0)
);
// delete rows that don't match the filter
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 0, 0, 0);
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 0, 1, 0);
execute("DELETE FROM %s WHERE a = ? AND b = ?", 0, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 0, 1),
row(1, 1, 1, 0),
row(1, 1, 2, 0)
);
// delete a row that does match the filter
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 1, 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 0, 0, 0),
row(1, 0, 1, 0),
row(1, 1, 1, 0),
row(1, 1, 2, 0)
);
// delete a partition that matches the filter
execute("DELETE FROM %s WHERE a = ? AND b = ?", 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 1, 1, 0),
row(1, 1, 2, 0));
execute("DELETE FROM %s WHERE a = ? AND b = ?", 1, 1);
assertEmpty(execute("SELECT * FROM mv_test" + i));
}
}
@Test
public void testPartitionKeyFilteringWithSlice() throws Throwable
{
List<String> mvPrimaryKeys = Arrays.asList("((a, b), c)", "((b, a), c)", "(a, b, c)", "(c, b, a)", "((c, a), b)");
for (int i = 0; i < mvPrimaryKeys.size(); i++)
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY ((a, b), c))");
execute("USE " + keyspace());
executeNet(protocolVersion, "USE " + keyspace());
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 1);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 10, 1, 2);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 2, 1);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 10, 2, 2);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 1, 3, 1);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 10, 3, 2);
logger.info("Testing MV primary key: {}", mvPrimaryKeys.get(i));
// only accept rows where a = 1
String viewName= "mv_test" + i;
createView(viewName, "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE a > 0 AND b > 5 AND c IS NOT NULL PRIMARY KEY " + mvPrimaryKeys.get(i));
waitForView(keyspace(), viewName);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2)
);
// insert new rows that do not match the filter
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 0, 0, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 1, 0, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2)
);
// insert new row that does match the filter
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 3, 10, 4, 2);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2),
row(3, 10, 4, 2)
);
// update rows that don't match the filter
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 1, 0, 0, 0);
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 1, 0, 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2),
row(3, 10, 4, 2)
);
// update a row that does match the filter
execute("UPDATE %s SET d = ? WHERE a = ? AND b = ? AND c = ?", 100, 3, 10, 4);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2),
row(3, 10, 4, 100)
);
// delete rows that don't match the filter
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 0, 0, 0);
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 0, 1, 0);
execute("DELETE FROM %s WHERE a = ? AND b = ?", 0, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2),
row(3, 10, 4, 100)
);
// delete a row that does match the filter
execute("DELETE FROM %s WHERE a = ? AND b = ? AND c = ?", 1, 1, 0);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(1, 10, 2, 2),
row(2, 10, 3, 2),
row(3, 10, 4, 100)
);
// delete a partition that matches the filter
execute("DELETE FROM %s WHERE a = ? AND b = ?", 1, 10);
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
row(2, 10, 3, 2),
row(3, 10, 4, 100));
}
}
private static void waitForView(String keyspace, String view) throws InterruptedException
{
while (!SystemKeyspace.isViewBuilt(keyspace, view))
Thread.sleep(10);
}
@Test
public void testPartitionKeyRestrictions() throws Throwable
{

View File

@ -117,6 +117,19 @@ public class ViewTest extends CQLTester
Assert.assertEquals(0, execute("select * from view1").size());
}
@Test
public void createMvWithUnrestrictedPKParts() throws Throwable
{
createTable("CREATE TABLE %s (k1 int, c1 int , val int, PRIMARY KEY (k1, c1))");
execute("USE " + keyspace());
executeNet(protocolVersion, "USE " + keyspace());
createView("view1", "CREATE MATERIALIZED VIEW view1 AS SELECT k1 FROM %%s WHERE k1 IS NOT NULL AND c1 IS NOT NULL AND val IS NOT NULL PRIMARY KEY (val, k1, c1)");
}
@Test
public void testClusteringKeyTombstone() throws Throwable
{

View File

@ -1177,6 +1177,101 @@ public class SecondaryIndexTest extends CQLTester
row(bytes("foo124"), EMPTY_BYTE_BUFFER));
}
@Test
public void testPartitionKeyWithIndex() throws Throwable
{
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a, b)))");
createIndex("CREATE INDEX ON %s (a);");
createIndex("CREATE INDEX ON %s (b);");
execute("INSERT INTO %s (a, b, c) VALUES (1,2,3)");
execute("INSERT INTO %s (a, b, c) VALUES (2,3,4)");
execute("INSERT INTO %s (a, b, c) VALUES (5,6,7)");
beforeAndAfterFlush(() -> {
assertRows(execute("SELECT * FROM %s WHERE a = 1"),
row(1, 2, 3));
assertRows(execute("SELECT * FROM %s WHERE b = 3"),
row(2, 3, 4));
});
}
@Test
public void testAllowFilteringOnPartitionKeyWithSecondaryIndex() throws Throwable
{
createTable("CREATE TABLE %s (pk1 int, pk2 int, c1 int, c2 int, v int, " +
"PRIMARY KEY ((pk1, pk2), c1, c2))");
createIndex("CREATE INDEX v_idx_1 ON %s (v);");
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= 2; j++)
{
execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, 1, 1, i);
execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, 1, i, i);
execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, 1, i, i, i);
execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", j, i, i, i, i);
}
}
beforeAndAfterFlush(() -> {
assertEmpty(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 0 AND c1 < 5 AND c2 = 1 AND v = 3 ALLOW FILTERING;"));
assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 0 AND c1 < 5 AND c2 = 3 AND v = 3 ALLOW FILTERING;"),
row(1, 3, 3, 3, 3),
row(1, 1, 1, 3, 3),
row(1, 1, 3, 3, 3));
assertEmpty(execute("SELECT * FROM %s WHERE pk1 = 1 AND c2 > 1 AND c2 < 5 AND v = 1 ALLOW FILTERING;"));
assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND c1 > 1 AND c2 > 2 AND v = 3 ALLOW FILTERING;"),
row(1, 3, 3, 3, 3),
row(1, 1, 3, 3, 3));
assertRows(execute("SELECT * FROM %s WHERE pk1 = 1 AND pk2 > 1 AND c2 > 2 AND v = 3 ALLOW FILTERING;"),
row(1, 3, 3, 3, 3));
assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk2 > 1 AND c1 IN(0,1,2) AND v <= 3 ALLOW FILTERING;"),
row(1, 2, 2, 2, 2),
row(2, 2, 2, 2, 2));
assertRows(execute("SELECT * FROM %s WHERE pk1 >= 2 AND pk2 <=3 AND c1 IN(0,1,2) AND c2 IN(0,1,2) AND v < 3 ALLOW FILTERING;"),
row(2, 2, 2, 2, 2),
row(2, 1, 1, 2, 2),
row(2, 1, 2, 2, 2));
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE pk1 >= 1 AND pk2 <=3 AND c1 IN(0,1,2) AND c2 IN(0,1,2) AND v = 3");
});
}
@Test
public void testAllowFilteringOnPartitionKeyWithIndexForContains() throws Throwable
{
createTable("CREATE TABLE %s (k1 int, k2 int, v set<int>, PRIMARY KEY ((k1, k2)))");
createIndex("CREATE INDEX ON %s(k2)");
execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 0, 0, set(1, 2, 3));
execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 0, 1, set(2, 3, 4));
execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 1, 0, set(3, 4, 5));
execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 1, 1, set(4, 5, 6));
beforeAndAfterFlush(() -> {
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE k2 > ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k2 > ? ALLOW FILTERING", 0),
row(0, 1, set(2, 3, 4)),
row(1, 1, set(4, 5, 6)));
assertRows(execute("SELECT * FROM %s WHERE k2 >= ? AND v CONTAINS ? ALLOW FILTERING", 1, 6),
row(1, 1, set(4, 5, 6)));
assertEmpty(execute("SELECT * FROM %s WHERE k2 < ? AND v CONTAINS ? ALLOW FILTERING", 0, 7));
});
}
private ResultMessage.Prepared prepareStatement(String cql, boolean forThrift)
{
return QueryProcessor.prepare(String.format(cql, KEYSPACE, currentTable()),

View File

@ -958,11 +958,14 @@ public class SelectMultiColumnRelationTest extends CQLTester
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
"SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?), (?, ?)) ALLOW FILTERING", 0, 1, 1, 2, 1);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?)) ALLOW FILTERING", 0, 1, 1),
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
"SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1),
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5),
row(0, 0, 2, 0, 0, 5));
assertRows(execute("SELECT * FROM %s WHERE a = ? AND b = ? AND (c) IN ((?)) AND f = ?", 0, 0, 1, 5),
row(0, 0, 1, 1, 1, 5));

View File

@ -27,8 +27,11 @@ import org.junit.Test;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.dht.ByteOrderedPartitioner;
/**
@ -42,6 +45,33 @@ public class SelectOrderedPartitionerTest extends CQLTester
DatabaseDescriptor.setPartitionerUnsafe(ByteOrderedPartitioner.instance);
}
@Test
public void testFilteringOnPartitionKeyWithToken() throws Throwable
{
createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY ((a, b), c))");
for (int i = 0; i < 10; i++)
{
execute("INSERT INTO %s (a,b,c,d) VALUES (?, ?, ?, ?)", i, i, i, i);
execute("INSERT INTO %s (a,b,c,d) VALUES (?, ?, ?, ?)", i, i + 10, i + 10, i + 10);
}
beforeAndAfterFlush(() -> {
assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE token(a, b) > token(5, 10) AND b < 8 ALLOW FILTERING"),
row(6, 6, 6, 6),
row(7, 7, 7, 7));
assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(8, 10) AND a = 9 ALLOW FILTERING"),
row(9, 9, 9, 9),
row(9, 19, 19, 19));
assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(8, 10) AND a = 9 AND c = 19 ALLOW FILTERING"),
row(9, 19, 19, 19));
assertEmpty(execute("SELECT * FROM %s WHERE token(a, b) = token(8, 8) AND b = 9 ALLOW FILTERING"));
});
}
@Test
public void testTokenFunctionWithSingleColumnPartitionKey() throws Throwable
{
@ -130,7 +160,7 @@ public class SelectOrderedPartitionerTest extends CQLTester
assertRows(execute("SELECT * FROM %s WHERE token(a) < token(?) AND token(a) >= token(?) AND a IN (?, ?);",
1, 3, 1, 3),
row(3, 3));
assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a > ?;", 1, 3, 1);
assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a IN ?;",
@ -206,7 +236,7 @@ public class SelectOrderedPartitionerTest extends CQLTester
assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) = token(?, ?) AND a = ?;",
0, 1, 0, 0, 1));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND a = ?;", 0, 0, 1);
}

View File

@ -206,9 +206,9 @@ public class SelectSingleColumnRelationTest extends CQLTester
row("second", 1, 1, 1),
row("second", 4, 4, 4));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"select * from %s where a in (?, ?)", "first", "second");
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"select * from %s where a = ?", "first");
assertInvalidMessage("b cannot be restricted by more than one relation if it includes a IN",
"select * from %s where a = ? AND b IN (?, ?) AND b = ?", "first", 2, 2, 3);
@ -498,11 +498,14 @@ public class SelectSingleColumnRelationTest extends CQLTester
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
"SELECT * FROM %s WHERE a = ? AND c IN (?, ?) AND d IN (?) ALLOW FILTERING", 0, 0, 1, 1);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND c IN (?) AND d IN (?) ALLOW FILTERING", 0, 1, 1),
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
"SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1),
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5),
row(0, 0, 2, 0, 0, 5));
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE a = ? AND c IN (?, ?) AND f = ?", 0, 0, 1, 5);
@ -520,8 +523,11 @@ public class SelectSingleColumnRelationTest extends CQLTester
assertRows(execute("SELECT * FROM %s WHERE a = ? AND c IN (?, ?) AND d IN (?) AND f = ? ALLOW FILTERING", 0, 1, 3, 0, 3),
row(0, 0, 1, 0, 0, 3));
assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
"SELECT * FROM %s WHERE a = ? AND c >= ? ALLOW FILTERING", 0, 1);
assertRows(execute("SELECT * FROM %s WHERE a = ? AND c >= ? ALLOW FILTERING", 0, 1),
row(0, 0, 1, 0, 0, 3),
row(0, 0, 1, 1, 0, 4),
row(0, 0, 1, 1, 1, 5),
row(0, 0, 2, 0, 0, 5));
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE a = ? AND c >= ? AND f = ?", 0, 1, 5);
@ -592,7 +598,7 @@ public class SelectSingleColumnRelationTest extends CQLTester
public void testInvalidSliceRestrictionOnPartitionKey() throws Throwable
{
createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c text)");
assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
"SELECT * FROM %s WHERE a >= 1 and a < 4");
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
"SELECT * FROM %s WHERE (a) >= (1) and (a) < (4)");
@ -604,9 +610,9 @@ public class SelectSingleColumnRelationTest extends CQLTester
createTable("CREATE TABLE %s (a int, b int, c text, PRIMARY KEY ((a, b)))");
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
"SELECT * FROM %s WHERE (a, b) >= (1, 1) and (a, b) < (4, 1)");
assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
"SELECT * FROM %s WHERE a >= 1 and (a, b) < (4, 1)");
assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
"SELECT * FROM %s WHERE b >= 1 and (a, b) < (4, 1)");
assertInvalidMessage("Multi-column relations can only be applied to clustering columns but was applied to: a",
"SELECT * FROM %s WHERE (a, b) >= (1, 1) and (b) < (4)");

View File

@ -112,7 +112,7 @@ public class CassandraIndexTest extends CQLTester
.indexName("k1_index")
.withFirstRow(row(0, 0, 0, 0, 0))
.withSecondRow(row(1, 1, 1, 1, 1))
.missingIndexMessage("Partition key parts: k2 must be restricted as other parts are")
.missingIndexMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE)
.firstQueryExpression("k1=0")
.secondQueryExpression("k1=1")
.run();
@ -127,7 +127,7 @@ public class CassandraIndexTest extends CQLTester
.indexName("k2_index")
.withFirstRow(row(0, 0, 0, 0, 0))
.withSecondRow(row(1, 1, 1, 1, 1))
.missingIndexMessage("Partition key parts: k1 must be restricted as other parts are")
.missingIndexMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE)
.firstQueryExpression("k2=0")
.secondQueryExpression("k2=1")
.run();