diff --git a/CHANGES.txt b/CHANGES.txt
index 85fca0f83715..7441abdf59bf 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
3.5
+ * Fix clustering and row filters for LIKE queries on clustering columns (CASSANDRA-11397)
Merged from 3.0:
* Allocate merkletrees with the correct size (CASSANDRA-11390)
* Support streaming pre-3.0 sstables (CASSANDRA-10990)
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
index 978ebbc8d614..7d0c3dfc2c07 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
@@ -54,6 +54,11 @@ final class PrimaryKeyRestrictionSet extends AbstractPrimaryKeyRestrictions
*/
private boolean in;
+ /**
+ * true
if the restrictions are corresponding to a LIKE, false
otherwise.
+ */
+ private boolean like;
+
/**
* true
if the restrictions are corresponding to a Slice, false
otherwise.
*/
@@ -106,6 +111,8 @@ else if (restriction.isContains() || primaryKeyRestrictions.isContains())
this.contains = true;
else if (restriction.isIN() || primaryKeyRestrictions.isIN())
this.in = true;
+ else if (restriction.isLIKE() || primaryKeyRestrictions.isLIKE())
+ this.like = true;
else
this.eq = true;
}
@@ -137,6 +144,12 @@ public boolean isIN()
return in;
}
+ @Override
+ public boolean isLIKE()
+ {
+ return like;
+ }
+
@Override
public boolean isContains()
{
@@ -220,7 +233,7 @@ public NavigableSet boundsAsClustering(Bound bound, QueryOptions op
{
ColumnDefinition def = r.getFirstColumn();
- if (keyPosition != def.position() || r.isContains())
+ if (keyPosition != def.position() || r.isContains() || r.isLIKE())
break;
if (r.isSlice())
@@ -296,7 +309,7 @@ public void addRowFilterTo(RowFilter filter,
ColumnDefinition columnDef = restriction.getFirstColumn();
// We ignore all the clustering columns that can be handled by slices.
- if (!isPartitionKey && !restriction.isContains()&& position == columnDef.position())
+ if (!isPartitionKey && !(restriction.isContains() || restriction.isLIKE()) && position == columnDef.position())
{
position = restriction.getLastColumn().position() + 1;
if (!restriction.hasSupportingIndex(indexManager))
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index d237d027039b..ab4815aa37a5 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -704,10 +704,10 @@ public void addRowFilterTo(RowFilter filter,
@Override
public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options)
{
- // LIKE could be used with clustering columns as soon as they are indexed,
- // but we have to hide such expression from clustering filter since it
- // can only filter based on the complete values.
- return builder;
+ // LIKE can be used with clustering columns, but as it doesn't
+ // represent an actual clustering value, it can't be used in a
+ // clustering filter.
+ throw new UnsupportedOperationException();
}
@Override
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index 123e33bd80df..0672a995e450 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -737,7 +737,7 @@ public boolean isColumnRange()
// this would mean a 'SELECT *' on a static compact table would query whole partitions, even though we'll only return
// the static part as far as CQL is concerned. This is thus mostly an optimization to use the query-by-name path).
int numberOfClusteringColumns = cfm.isStaticCompactTable() ? 0 : cfm.clusteringColumns().size();
- // it is a range query if it has at least one the column alias for which no relation is defined or is not EQ.
+ // it is a range query if it has at least one the column alias for which no relation is defined or is not EQ or IN.
return clusteringColumnsRestrictions.size() < numberOfClusteringColumns
|| (!clusteringColumnsRestrictions.isEQ() && !clusteringColumnsRestrictions.isIN());
}
diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java
index 9a8c4241e9ed..e68dd946e40a 100644
--- a/test/unit/org/apache/cassandra/SchemaLoader.java
+++ b/test/unit/org/apache/cassandra/SchemaLoader.java
@@ -577,6 +577,11 @@ public static CFMetaData sasiCFMD(String ksName, String cfName)
}
public static CFMetaData clusteringSASICFMD(String ksName, String cfName)
+ {
+ return clusteringSASICFMD(ksName, cfName, "location", "age", "height", "score");
+ }
+
+ public static CFMetaData clusteringSASICFMD(String ksName, String cfName, String...indexedColumns)
{
CFMetaData cfm = CFMetaData.Builder.create(ksName, cfName)
.addPartitionKey("name", UTF8Type.instance)
@@ -586,32 +591,17 @@ public static CFMetaData clusteringSASICFMD(String ksName, String cfName)
.addRegularColumn("score", DoubleType.instance)
.build();
- cfm.indexes(cfm.getIndexes()
- .with(IndexMetadata.fromSchemaMetadata("location", IndexMetadata.Kind.CUSTOM, new HashMap()
- {{
- put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
- put(IndexTarget.TARGET_OPTION_NAME, "location");
- put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString());
- }}))
- .with(IndexMetadata.fromSchemaMetadata("age", IndexMetadata.Kind.CUSTOM, new HashMap()
- {{
- put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
- put(IndexTarget.TARGET_OPTION_NAME, "age");
- put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString());
- }}))
- .with(IndexMetadata.fromSchemaMetadata("height", IndexMetadata.Kind.CUSTOM, new HashMap()
- {{
- put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
- put(IndexTarget.TARGET_OPTION_NAME, "height");
- put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString());
- }}))
- .with(IndexMetadata.fromSchemaMetadata("score", IndexMetadata.Kind.CUSTOM, new HashMap()
- {{
- put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
- put(IndexTarget.TARGET_OPTION_NAME, "score");
- put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString());
- }})));
-
+ Indexes indexes = cfm.getIndexes();
+ for (String indexedColumn : indexedColumns)
+ {
+ indexes = indexes.with(IndexMetadata.fromSchemaMetadata(indexedColumn, IndexMetadata.Kind.CUSTOM, new HashMap()
+ {{
+ put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName());
+ put(IndexTarget.TARGET_OPTION_NAME, indexedColumn);
+ put("mode", OnDiskIndexBuilder.Mode.PREFIX.toString());
+ }}));
+ }
+ cfm.indexes(indexes);
return cfm;
}
diff --git a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
index c9d66f7b8585..ca6e9a1be1be 100644
--- a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
+++ b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java
@@ -80,7 +80,8 @@ public class SASIIndexTest
private static final String KS_NAME = "sasi";
private static final String CF_NAME = "test_cf";
- private static final String CLUSTERING_CF_NAME = "clustering_test_cf";
+ private static final String CLUSTERING_CF_NAME_1 = "clustering_test_cf_1";
+ private static final String CLUSTERING_CF_NAME_2 = "clustering_test_cf_2";
@BeforeClass
public static void loadSchema() throws ConfigurationException
@@ -90,7 +91,8 @@ public static void loadSchema() throws ConfigurationException
MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(KS_NAME,
KeyspaceParams.simpleTransient(1),
Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME),
- SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME))));
+ SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME_1),
+ SchemaLoader.clusteringSASICFMD(KS_NAME, CLUSTERING_CF_NAME_2, "location"))));
}
@After
@@ -1621,64 +1623,64 @@ public void testClusteringIndexes() throws Exception
public void testClusteringIndexes(boolean forceFlush) throws Exception
{
- ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTERING_CF_NAME);
+ ColumnFamilyStore store = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTERING_CF_NAME_1);
- executeCQL("INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "US", 27, 183, 1.0);
- executeCQL("INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "BY", 28, 182, 2.0);
- executeCQL("INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Jordan", "US", 27, 182, 1.0);
+ executeCQL(CLUSTERING_CF_NAME_1, "INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "US", 27, 183, 1.0);
+ executeCQL(CLUSTERING_CF_NAME_1, "INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Pavel", "BY", 28, 182, 2.0);
+ executeCQL(CLUSTERING_CF_NAME_1 ,"INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Jordan", "US", 27, 182, 1.0);
if (forceFlush)
store.forceBlockingFlush();
UntypedResultSet results;
- results = executeCQL("SELECT * FROM %s.%s WHERE location = ? ALLOW FILTERING", "US");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location = ? ALLOW FILTERING", "US");
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE age >= ? AND height = ? ALLOW FILTERING", 27, 182);
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE age >= ? AND height = ? ALLOW FILTERING", 27, 182);
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE age = ? AND height = ? ALLOW FILTERING", 28, 182);
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE age = ? AND height = ? ALLOW FILTERING", 28, 182);
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE age >= ? AND height = ? AND score >= ? ALLOW FILTERING", 27, 182, 1.0);
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE age >= ? AND height = ? AND score >= ? ALLOW FILTERING", 27, 182, 1.0);
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE age >= ? AND height = ? AND score = ? ALLOW FILTERING", 27, 182, 1.0);
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE age >= ? AND height = ? AND score = ? ALLOW FILTERING", 27, 182, 1.0);
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location = ? AND age >= ? ALLOW FILTERING", "US", 27);
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location = ? AND age >= ? ALLOW FILTERING", "US", 27);
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location = ? ALLOW FILTERING", "BY");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location = ? ALLOW FILTERING", "BY");
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'U%%' ALLOW FILTERING");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE 'U%%' ALLOW FILTERING");
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'U%%' AND height >= 183 ALLOW FILTERING");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE 'U%%' AND height >= 183 ALLOW FILTERING");
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'US%%' ALLOW FILTERING");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE 'US%%' ALLOW FILTERING");
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
- results = executeCQL("SELECT * FROM %s.%s WHERE location LIKE 'US' ALLOW FILTERING");
+ results = executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE 'US' ALLOW FILTERING");
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
try
{
- executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%U' ALLOW FILTERING");
+ executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE '%%U' ALLOW FILTERING");
Assert.fail();
}
catch (InvalidRequestException e)
@@ -1689,7 +1691,7 @@ public void testClusteringIndexes(boolean forceFlush) throws Exception
try
{
- executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%' ALLOW FILTERING");
+ executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE '%%' ALLOW FILTERING");
Assert.fail();
}
catch (SyntaxException e)
@@ -1700,7 +1702,7 @@ public void testClusteringIndexes(boolean forceFlush) throws Exception
try
{
- executeCQL("SELECT * FROM %s.%s WHERE location LIKE '%%%%' ALLOW FILTERING");
+ executeCQL(CLUSTERING_CF_NAME_1 ,"SELECT * FROM %s.%s WHERE location LIKE '%%%%' ALLOW FILTERING");
Assert.fail();
}
catch (SyntaxException e)
@@ -1708,6 +1710,19 @@ public void testClusteringIndexes(boolean forceFlush) throws Exception
Assert.assertTrue(e.getMessage().contains("empty"));
// expected
}
+
+ // check restrictions on non-indexed clustering columns when preceding columns are indexed
+ store = Keyspace.open(KS_NAME).getColumnFamilyStore(CLUSTERING_CF_NAME_2);
+ executeCQL(CLUSTERING_CF_NAME_2 ,"INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Tony", "US", 43, 184, 2.0);
+ executeCQL(CLUSTERING_CF_NAME_2 ,"INSERT INTO %s.%s (name, location, age, height, score) VALUES (?, ?, ?, ?, ?)", "Christopher", "US", 27, 180, 1.0);
+
+ if (forceFlush)
+ store.forceBlockingFlush();
+
+ results = executeCQL(CLUSTERING_CF_NAME_2 ,"SELECT * FROM %s.%s WHERE location LIKE 'US' AND age = 43 ALLOW FILTERING");
+ Assert.assertNotNull(results);
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals("Tony", results.one().getString("name"));
}
@Test
@@ -2037,7 +2052,7 @@ private void cleanupData()
{
Keyspace ks = Keyspace.open(KS_NAME);
ks.getColumnFamilyStore(CF_NAME).truncateBlocking();
- ks.getColumnFamilyStore(CLUSTERING_CF_NAME).truncateBlocking();
+ ks.getColumnFamilyStore(CLUSTERING_CF_NAME_1).truncateBlocking();
}
private static Set getIndexed(ColumnFamilyStore store, int maxResults, Expression... expressions)
@@ -2150,9 +2165,9 @@ private static List convert(final Set keys)
}};
}
- private UntypedResultSet executeCQL(String query, Object... values)
+ private UntypedResultSet executeCQL(String cfName, String query, Object... values)
{
- return QueryProcessor.executeOnceInternal(String.format(query, KS_NAME, CLUSTERING_CF_NAME), values);
+ return QueryProcessor.executeOnceInternal(String.format(query, KS_NAME, cfName), values);
}
private Set executeCQLWithKeys(String rawStatement) throws Exception