You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by bl...@apache.org on 2021/09/08 07:58:34 UTC

[cassandra] branch trunk updated: Add support for filtering using IN restrictions

This is an automated email from the ASF dual-hosted git repository.

blerer pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new dcee430  Add support for filtering using IN restrictions
dcee430 is described below

commit dcee430e57f3fd37e4bc6652ad7180361e31444e
Author: nvharikrishna <n....@gmail.com>
AuthorDate: Wed Aug 18 15:06:03 2021 +0200

    Add support for filtering using IN restrictions
    
    patch by Venkata Harikrishna Nukala; reviewed by Andrés de la Peña and
    Benjamin Lerer for CASSANDRA-14344
---
 CHANGES.txt                                        |   1 +
 src/java/org/apache/cassandra/cql3/Operator.java   |   5 +-
 .../cassandra/cql3/SingleColumnRelation.java       |  10 --
 .../cql3/restrictions/MultiColumnRestriction.java  |  19 ++-
 .../cql3/restrictions/SingleColumnRestriction.java |   7 +-
 .../cql3/restrictions/StatementRestrictions.java   |   3 -
 .../org/apache/cassandra/db/filter/RowFilter.java  |   6 +-
 .../cassandra/index/sasi/conf/ColumnIndex.java     |   2 +-
 .../cassandra/index/sasi/plan/Expression.java      |   5 +-
 .../cassandra/serializers/ListSerializer.java      |  37 ++++++
 .../validation/operations/CompactStorageTest.java  |  26 ++--
 .../operations/SelectMultiColumnRelationTest.java  |  42 ++++++
 .../operations/SelectSingleColumnRelationTest.java | 146 ++++++++++++++++++++-
 .../cql3/validation/operations/SelectTest.java     |  64 ++++++---
 .../apache/cassandra/index/sasi/SASICQLTest.java   |  16 +++
 15 files changed, 334 insertions(+), 55 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index c3000d4..5d4c20f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.1
+ * Add support for filtering using IN restrictions (CASSANDRA-14344)
  * Provide a nodetool command to invalidate auth caches (CASSANDRA-16404)
  * Catch read repair timeout exceptions and add metric (CASSANDRA-16880)
  * Exclude Jackson 1.x transitive dependency of hadoop* provided dependencies (CASSANDRA-16854)
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java b/src/java/org/apache/cassandra/cql3/Operator.java
index 1acedee..992056c 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.serializers.ListSerializer;
 import org.apache.cassandra.utils.ByteBufferUtil;
 
 public enum Operator
@@ -110,8 +111,8 @@ public enum Operator
 
         public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer leftOperand, ByteBuffer rightOperand)
         {
-            List<?> inValues = ListType.getInstance(type, false).getSerializer().deserialize(rightOperand);
-            return inValues.contains(type.getSerializer().deserialize(leftOperand));
+            ListSerializer<?> serializer = ListType.getInstance(type, false).getSerializer();
+            return serializer.anyMatch(rightOperand, r -> type.compareForCQL(leftOperand, r) == 0);
         }
     },
     CONTAINS(5)
diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
index 9ff3f07..cf1cb69 100644
--- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
+++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java
@@ -274,16 +274,6 @@ public final class SingleColumnRelation extends Relation
     {
         ColumnSpecification receiver = columnDef;
 
-        if (isIN())
-        {
-            // We only allow IN on the row key and the clustering key so far, never on non-PK columns, and this even if
-            // there's an index
-            // Note: for backward compatibility reason, we conside a IN of 1 value the same as a EQ, so we let that
-            // slide.
-            checkFalse(!columnDef.isPrimaryKeyColumn() && !canHaveOnlyOneValue(),
-                       "IN predicates on non-primary-key columns (%s) is not yet supported", columnDef.name);
-        }
-
         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);
 
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
index 4c6ce2f..acbb48e 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
@@ -21,6 +21,8 @@ import java.nio.ByteBuffer;
 import java.util.*;
 
 import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.serializers.ListSerializer;
+import org.apache.cassandra.transport.ProtocolVersion;
 import org.apache.cassandra.cql3.*;
 import org.apache.cassandra.cql3.Term.Terminal;
 import org.apache.cassandra.cql3.functions.Function;
@@ -247,7 +249,22 @@ public abstract class MultiColumnRestriction implements SingleRestriction
                                          IndexRegistry indexRegistry,
                                          QueryOptions options)
         {
-            throw  invalidRequest("IN restrictions are not supported on indexed columns");
+            // If the relation is of the type (c) IN ((x),(y),(z)) then it is equivalent to
+            // c IN (x, y, z) and we can perform filtering
+            if (getColumnDefs().size() == 1)
+            {
+                List<List<ByteBuffer>> splitValues = splitValues(options);
+                List<ByteBuffer> values = new ArrayList<>(splitValues.size());
+                for (List<ByteBuffer> splitValue : splitValues)
+                    values.add(splitValue.get(0));
+
+                ByteBuffer buffer = ListSerializer.pack(values, values.size(), ProtocolVersion.V3);
+                filter.add(getFirstColumn(), Operator.IN, buffer);
+            }
+            else
+            {
+                throw invalidRequest("Multicolumn IN filters are not supported");
+            }
         }
 
         protected abstract List<List<ByteBuffer>> splitValues(QueryOptions options);
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index 1b3482b..a50a0f3 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -23,6 +23,9 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.serializers.CollectionSerializer;
+import org.apache.cassandra.serializers.ListSerializer;
+import org.apache.cassandra.transport.ProtocolVersion;
 import org.apache.cassandra.cql3.*;
 import org.apache.cassandra.cql3.Term.Terminal;
 import org.apache.cassandra.cql3.functions.Function;
@@ -218,7 +221,9 @@ public abstract class SingleColumnRestriction implements SingleRestriction
                                    IndexRegistry indexRegistry,
                                    QueryOptions options)
         {
-            throw invalidRequest("IN restrictions are not supported on indexed columns");
+            List<ByteBuffer> values = getValues(options);
+            ByteBuffer buffer = ListSerializer.pack(values, values.size(), ProtocolVersion.V3);
+            filter.add(columnDef, Operator.IN, buffer);
         }
 
         @Override
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index d1837d4..b61cb4a 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -440,9 +440,6 @@ public final class StatementRestrictions
                 if (!allowFiltering && !forView && !hasQueriableIndex)
                     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;
             }
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index 68cc194..5e0fb51 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -654,6 +654,7 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
             switch (operator)
             {
                 case EQ:
+                case IN:
                 case LT:
                 case LTE:
                 case GTE:
@@ -746,11 +747,6 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
                         ByteBuffer foundValue = getValue(metadata, partitionKey, row);
                         return foundValue != null && mapType.getSerializer().getSerializedValue(foundValue, value, mapType.getKeysType()) != null;
                     }
-
-                case IN:
-                    // It wouldn't be terribly hard to support this (though doing so would imply supporting
-                    // IN for 2ndary index) but currently we don't.
-                    throw new AssertionError();
             }
             throw new AssertionError();
         }
diff --git a/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java b/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
index 4c9c59e..5ae1cc9 100644
--- a/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
+++ b/src/java/org/apache/cassandra/index/sasi/conf/ColumnIndex.java
@@ -223,10 +223,10 @@ public class ColumnIndex
 
         Op operator = Op.valueOf(op);
         return !(isTokenized && operator == Op.EQ) // EQ is only applicable to non-tokenized indexes
+               && operator != Op.IN // IN operator is not supported
                && !(isTokenized && mode.mode == OnDiskIndexBuilder.Mode.CONTAINS && operator == Op.PREFIX) // PREFIX not supported on tokenized CONTAINS mode indexes
                && !(isLiteral() && operator == Op.RANGE) // RANGE only applicable to indexes non-literal indexes
                && mode.supports(operator); // for all other cases let's refer to index itself
-
     }
 
     public static ByteBuffer getValueOf(ColumnMetadata column, Row row, int nowInSecs)
diff --git a/src/java/org/apache/cassandra/index/sasi/plan/Expression.java b/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
index 8de45e8..6c3b9b8 100644
--- a/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
+++ b/src/java/org/apache/cassandra/index/sasi/plan/Expression.java
@@ -47,7 +47,7 @@ public class Expression
 
     public enum Op
     {
-        EQ, MATCH, PREFIX, SUFFIX, CONTAINS, NOT_EQ, RANGE;
+        EQ, MATCH, PREFIX, SUFFIX, CONTAINS, NOT_EQ, RANGE, IN;
 
         public static Op valueOf(Operator operator)
         {
@@ -56,6 +56,9 @@ public class Expression
                 case EQ:
                     return EQ;
 
+                case IN:
+                    return IN;
+
                 case NEQ:
                     return NOT_EQ;
 
diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java b/src/java/org/apache/cassandra/serializers/ListSerializer.java
index 240e0a6..429be98 100644
--- a/src/java/org/apache/cassandra/serializers/ListSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java
@@ -20,9 +20,11 @@ package org.apache.cassandra.serializers;
 
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
 
 import org.apache.cassandra.db.TypeSizes;
 import org.apache.cassandra.db.marshal.AbstractType;
@@ -127,6 +129,41 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
         }
     }
 
+    public boolean anyMatch(ByteBuffer serializedList, Predicate<ByteBuffer> predicate)
+    {
+        return anyMatch(serializedList, ByteBufferAccessor.instance, predicate);
+    }
+
+    public <V> boolean anyMatch(V input, ValueAccessor<V> accessor, Predicate<V> predicate)
+    {
+        try
+        {
+            int s = readCollectionSize(input, accessor, ProtocolVersion.V3);
+            int offset = sizeOfCollectionSize(s, ProtocolVersion.V3);
+
+            for (int i = 0; i < s; i++)
+            {
+                int size = accessor.getInt(input, offset);
+                if (size < 0)
+                    continue;
+
+                offset += TypeSizes.INT_SIZE;
+
+                V value = accessor.slice(input, offset, size);
+
+                if (predicate.test(value))
+                    return true;
+
+                offset += size;
+            }
+            return false;
+        }
+        catch (BufferUnderflowException e)
+        {
+            throw new MarshalException("Not enough bytes to read a list");
+        }
+    }
+
     /**
      * Returns the element at the given index in a list.
      * @param input a serialized list
diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java
index 87ffa05..70d3fc3 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java
@@ -2446,11 +2446,12 @@ public class CompactStorageTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = 4 ALLOW FILTERING"),
                        row(1, 4, 4));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7)");
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
-                                 "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
+            assertRows(execute("SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING"),
+                       row(1, 3, 6),
+                       row(2, 3, 7));
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE c > 4");
@@ -2517,11 +2518,12 @@ public class CompactStorageTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = 4 ALLOW FILTERING"),
                        row(1, 2, 4));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7)");
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
-                                 "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
+            assertRows(execute("SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING"),
+                       row(2, 1, 6));
+
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE c > 4");
@@ -3484,11 +3486,11 @@ public class CompactStorageTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = 4 ALLOW FILTERING"),
                        row(1, 4, 4, 5));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (d) is not yet supported",
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE a IN (1, 2) AND b = 3 AND d IN (6, 7)");
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (d) is not yet supported",
-                                 "SELECT * FROM %s WHERE a IN (1, 2) AND b = 3 AND d IN (6, 7) ALLOW FILTERING");
+            assertRows(execute("SELECT * FROM %s WHERE a IN (1, 2) AND b = 3 AND d IN (6, 7) ALLOW FILTERING"),
+                       row(1, 3, 6, 7));
 
             assertRows(execute("SELECT * FROM %s WHERE a < 2 AND c > 4 AND c <= 6 ALLOW FILTERING"),
                        row(1, 3, 6, 7));
@@ -3569,11 +3571,11 @@ public class CompactStorageTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND c >= 4 ALLOW FILTERING"),
                        row(1, 2, 4));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (b) is not yet supported",
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE a = 1 AND b IN (1, 2) AND c IN (6, 7)");
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
-                                 "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
+            assertRows(execute("SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING"),
+                       row(2, 1, 6));
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE c > 4");
diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
index 5062448..b69ee37 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
@@ -1930,4 +1930,46 @@ public class SelectMultiColumnRelationTest extends CQLTester
         assertInvalidMessage("Undefined column name e", "SELECT c AS e FROM %s WHERE (b, e) IN ((0, 1), (2, 4))");
         assertInvalidMessage("Undefined column name e", "SELECT c AS e FROM %s WHERE (b, e) > (0, 1) and b <= 2");
     }
+
+    @Test
+    public void testInRestrictionsWithAllowFiltering() throws Throwable
+    {
+        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);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "1", 0, 2, 4);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "1", 1, 3, 5);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "2", 1, 4, 6);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "2", 2, 5, 7);
+
+        assertRows(execute("SELECT * FROM %s WHERE (c2) IN ((?), (?)) ALLOW FILTERING", 1, 3),
+                   row(1, "1", 1, 3, 5),
+                   row(1, "2", 1, 4, 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE c2 IN (?, ?) ALLOW FILTERING", 1, 3),
+                   row(1, "1", 1, 3, 5),
+                   row(1, "2", 1, 4, 6));
+
+        assertInvalidMessage("Multicolumn IN filters are not supported",
+                             "SELECT * FROM %s WHERE (c2, c3) IN ((?, ?), (?, ?)) ALLOW FILTERING", 1, 0, 2, 0);
+    }
+
+    @Test
+    public void testInRestrictionsWithIndex() throws Throwable
+    {
+        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)");
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "0", 0, 1, 3);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "1", 0, 2, 4);
+        execute("INSERT INTO %s (pk, c1, c2, c3, v) values (?, ?, ?, ?, ?)", 1, "1", 1, 3, 5);
+
+        assertRows(execute("SELECT * FROM %s WHERE (c3) IN ((?), (?)) ALLOW FILTERING", 1, 3),
+                   row(1, "0", 0, 1, 3),
+                   row(1, "1", 1, 3, 5));
+
+        assertInvalidMessage("PRIMARY KEY column \"c2\" cannot be restricted as preceding column \"c1\" is not restricted",
+                             "SELECT * FROM %s WHERE (c2, c3) IN ((?, ?), (?, ?))", 1, 0, 2, 0);
+
+        assertInvalidMessage("Multicolumn IN filters are not supported",
+                             "SELECT * FROM %s WHERE (c2, c3) IN ((?, ?), (?, ?)) ALLOW FILTERING", 1, 0, 2, 0);
+    }
  }
diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
index 3795ce5..17c7e87 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
@@ -351,9 +351,17 @@ public class SelectSingleColumnRelationTest extends CQLTester
 
         assertRows(execute("SELECT v1 FROM %s WHERE time = 1"), row("B"), row("E"));
 
-        assertInvalidMessage("IN restrictions are not supported on indexed columns",
-                             "SELECT v1 FROM %s WHERE id2 = 0 and time IN (1, 2) ALLOW FILTERING");
+        // Checks that IN restrictions are not used for index queries
+        assertInvalidMessage("PRIMARY KEY column \"time\" cannot be restricted as preceding column \"author\" is not restricted",
+                            "SELECT v1 FROM %s WHERE time IN (1, 2)");
+        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                             "SELECT v1 FROM %s WHERE id2 IN (0, 2)");
+
+        // Checks that the IN queries works with filtering
+        assertRows(execute("SELECT v1 FROM %s WHERE time IN (1, 2) ALLOW FILTERING"), row("B"), row("C"), row("E"));
+        assertRows(execute("SELECT v1 FROM %s WHERE id2 IN (0, 2) ALLOW FILTERING"), row("A"), row("B"), row("D"));
 
+        // Checks index query with filtering
         assertRows(execute("SELECT v1 FROM %s WHERE author > 'ted' AND time = 1 ALLOW FILTERING"), row("E"));
         assertRows(execute("SELECT v1 FROM %s WHERE author > 'amy' AND author < 'zoe' AND time = 0 ALLOW FILTERING"),
                            row("A"), row("D"));
@@ -639,4 +647,138 @@ public class SelectSingleColumnRelationTest extends CQLTester
         assertInvalidMessage("Cannot use CONTAINS on non-collection column b",
                              "SELECT * FROM %s WHERE b CONTAINS ?", udt);
     }
+
+    @Test
+    public void testInRestrictionWithClusteringColumn() throws Throwable
+    {
+        createTable("CREATE TABLE %s (key int, c1 int, c2 int, s1 text static, PRIMARY KEY ((key, c1), c2))");
+
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 10, 11, 1, 's1')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 10, 12, 2, 's2')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 10, 13, 3, 's3')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 10, 13, 4, 's4')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 20, 21, 1, 's1')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 20, 22, 2, 's2')");
+        execute("INSERT INTO %s (key, c1, c2, s1) VALUES ( 20, 22, 3, 's3')");
+
+        assertRows(execute("SELECT * from %s WHERE key = ? AND c1 IN (?, ?)", 10, 21, 13),
+                   row(10, 13, 3, "s4"),
+                   row(10, 13, 4, "s4"));
+
+        assertRows(execute("SELECT * from %s WHERE key = ? AND c2 IN (?, ?) ALLOW FILTERING", 20, 1, 2),
+                   row(20, 22, 2, "s3"),
+                   row(20, 21, 1, "s1"));
+
+        assertRows(execute("SELECT * from %s WHERE c1 = ? AND c2 IN (?, ?) ALLOW FILTERING", 13, 2, 3),
+                   row(10, 13, 3, "s4"));
+
+        assertRowsIgnoringOrder(execute("SELECT * from %s WHERE c2 IN (?, ?) ALLOW FILTERING", 1, 2),
+                                row(10, 11, 1, "s1"),
+                                row(10, 12, 2, "s2"),
+                                row(20, 21, 1, "s1"),
+                                row(20, 22, 2, "s3"));
+
+        assertInvalidMessage("Invalid null value in condition for column c2",
+                             "SELECT * from %s WHERE key = 10 AND c2 IN (1, null) ALLOW FILTERING");
+    }
+
+    @Test
+    public void testInRestrictionsWithAllowFiltering() throws Throwable
+    {
+        createTable("CREATE TABLE %s (pk1 int, pk2 int, c text, s int static, v int, primary key((pk1, pk2), c))");
+        execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 1, 0, "5", 1, 3);
+        execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 1, 0, "7", 1, 2);
+        execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 1, 1, "7", 1, 3);
+        execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 2, 0, "4", 2, 1);
+        execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 2, 3, "6", 2, 8);
+
+        // Test filtering on regular columns
+        assertRows(execute("SELECT * FROM %s WHERE v IN (?, ?) ALLOW FILTERING", 4, 3),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 1, "7", 1, 3));
+
+        assertRows(execute("SELECT * FROM %s WHERE v IN ? ALLOW FILTERING", list(4, 3)),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 1, "7", 1, 3));
+
+        // Test filtering on clustering columns
+        assertRows(execute("SELECT * FROM %s WHERE c IN (?, ?, ?) ALLOW FILTERING", "7", "6", "8"),
+                   row(2, 3, "6", 2, 8),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+
+        assertRows(execute("SELECT * FROM %s WHERE c IN ? ALLOW FILTERING", list("7", "6", "8")),
+                   row(2, 3, "6", 2, 8),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+
+        // Test filtering on partition keys
+        assertRows(execute("SELECT * FROM %s WHERE pk1 IN (?, ?) ALLOW FILTERING", 1, 3),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk1 IN ? ALLOW FILTERING", list(1, 3)),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+
+        // Test filtering on static columns
+        assertRows(execute("SELECT * FROM %s WHERE s IN (?, ?) ALLOW FILTERING", 1, 3),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+
+        assertRows(execute("SELECT * FROM %s WHERE s IN ? ALLOW FILTERING", list(1, 3)),
+                   row(1, 0, "5", 1, 3),
+                   row(1, 0, "7", 1, 2),
+                   row(1, 1, "7", 1, 3));
+    }
+
+    @Test
+    public void testInRestrictionsWithAllowFilteringAndOrdering() throws Throwable
+    {
+        createTable("CREATE TABLE %s (pk int, c text, v int, primary key(pk, c)) WITH CLUSTERING ORDER BY (c DESC)");
+        execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 1, "0", 5);
+        execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 1, "1", 7);
+        execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 1, "2", 7);
+        execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 2, "0", 4);
+        execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 2, "2", 6);
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c IN (?, ?, ?) ALLOW FILTERING", 1, "2", "0", "8"),
+                   row(1, "2", 7),
+                   row(1, "0", 5));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c IN ? ORDER BY c ASC ALLOW FILTERING", 2, list("2", "8", "0")),
+                   row(2, "0", 4),
+                   row(2, "2", 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk IN (?, ?) AND c IN (?, ?, ?) ALLOW FILTERING", 1, 2, "2", "0", "8"),
+                   row(1, "2", 7),
+                   row(1, "0", 5),
+                   row(2, "2", 6),
+                   row(2, "0", 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk IN ? AND c IN ? ORDER BY c ASC ALLOW FILTERING", list(1, 2), list("2", "8", "0")),
+                   row(1, "0", 5),
+                   row(2, "0", 4),
+                   row(1, "2", 7),
+                   row(2, "2", 6));
+    }
+
+    @Test
+    public void testInRestrictionsWithCountersAndAllowFiltering() throws Throwable
+    {
+        createTable("CREATE TABLE %s (pk int, v counter, primary key (pk))");
+
+        assertEmpty(execute("SELECT * FROM %s WHERE v IN (?, ?) ALLOW FILTERING", 0L, 1L));
+
+        execute("UPDATE %s SET v = v + 1 WHERE pk = 1");
+        execute("UPDATE %s SET v = v + 2 WHERE pk = 2");
+        execute("UPDATE %s SET v = v + 1 WHERE pk = 3");
+
+        assertRows(execute("SELECT * FROM %s WHERE v IN (?, ?) ALLOW FILTERING", 0L, 1L),
+                   row(1, 1L),
+                   row(3, 1L));
+    }
 }
diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index d0493cf..7bbaa85 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@ -1276,11 +1276,12 @@ public class SelectTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE s = 1 AND d = 12 ALLOW FILTERING"),
                        row(1, 3, 1, 6, 12));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7)");
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
-                                 "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
+            assertRows(execute("SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING"),
+                       row(1, 3, 1, 6, 12),
+                       row(2, 3, 2, 7, 12));
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE c > 4");
@@ -1669,8 +1670,8 @@ public class SelectTest extends CQLTester
 
         beforeAndAfterFlush(() -> {
 
-            assertInvalidMessage("IN restrictions are not supported when the query involves filtering",
-                    "SELECT * FROM %s WHERE b in (11,12) ALLOW FILTERING");
+            assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                    "SELECT * FROM %s WHERE b in (11,12)");
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                     "SELECT * FROM %s WHERE a = 11");
@@ -1745,8 +1746,8 @@ public class SelectTest extends CQLTester
 
         beforeAndAfterFlush(() -> {
 
-             assertInvalidMessage("IN restrictions are not supported when the query involves filtering",
-                    "SELECT * FROM %s WHERE b in (11,12) ALLOW FILTERING");
+             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                    "SELECT * FROM %s WHERE b in (11,12)");
 
             assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                     "SELECT * FROM %s WHERE a = 11");
@@ -2838,8 +2839,9 @@ public class SelectTest extends CQLTester
                    row(0, Duration.from("1s")),
                    row(2, Duration.from("1s")));
 
-        assertInvalidMessage("IN predicates on non-primary-key columns (d) is not yet supported",
-                             "SELECT * FROM %s WHERE d IN (1s, 2s) ALLOW FILTERING");
+        assertRows(execute("SELECT * FROM %s WHERE d IN (1s, 3s) ALLOW FILTERING"),
+                   row(0, Duration.from("1s")),
+                   row(2, Duration.from("1s")));
 
         assertInvalidMessage("Slice restrictions are not supported on duration columns",
                              "SELECT * FROM %s WHERE d > 1s ALLOW FILTERING");
@@ -2867,11 +2869,19 @@ public class SelectTest extends CQLTester
             execute("INSERT INTO %s (k, l) VALUES (2, [1s, 3s])");
 
             if (frozen)
+            {
                 assertRows(execute("SELECT * FROM %s WHERE l = [1s, 2s] ALLOW FILTERING"),
                            row(0, list(Duration.from("1s"), Duration.from("2s"))));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (l) is not yet supported",
-                                 "SELECT * FROM %s WHERE l IN ([1s, 2s], [2s, 3s]) ALLOW FILTERING");
+                assertRows(execute("SELECT * FROM %s WHERE l IN ([1s, 2s], [2s, 3s]) ALLOW FILTERING"),
+                           row(1, list(Duration.from("2s"), Duration.from("3s"))),
+                           row(0, list(Duration.from("1s"), Duration.from("2s"))));
+            }
+            else
+            {
+                assertInvalidMessage("Collection column 'l' (list<duration>) cannot be restricted by a 'IN' relation",
+                        "SELECT * FROM %s WHERE l IN ([1s, 2s], [2s, 3s]) ALLOW FILTERING");
+            }
 
             assertInvalidMessage("Slice restrictions are not supported on collections containing durations",
                                  "SELECT * FROM %s WHERE l > [2s, 3s] ALLOW FILTERING");
@@ -2904,11 +2914,19 @@ public class SelectTest extends CQLTester
             execute("INSERT INTO %s (k, m) VALUES (2, {1:1s, 3:3s})");
 
             if (frozen)
+            {
                 assertRows(execute("SELECT * FROM %s WHERE m = {1:1s, 2:2s} ALLOW FILTERING"),
                            row(0, map(1, Duration.from("1s"), 2, Duration.from("2s"))));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (m) is not yet supported",
-                    "SELECT * FROM %s WHERE m IN ({1:1s, 2:2s}, {1:1s, 3:3s}) ALLOW FILTERING");
+                assertRows(execute("SELECT * FROM %s WHERE m IN ({1:1s, 2:2s}, {1:1s, 3:3s}) ALLOW FILTERING"),
+                        row(0, map(1, Duration.from("1s"), 2, Duration.from("2s"))),
+                        row(2, map(1, Duration.from("1s"), 3, Duration.from("3s"))));
+            }
+            else
+            {
+                assertInvalidMessage("Collection column 'm' (map<int, duration>) cannot be restricted by a 'IN' relation",
+                        "SELECT * FROM %s WHERE m IN ({1:1s, 2:2s}, {1:1s, 3:3s}) ALLOW FILTERING");
+            }
 
             assertInvalidMessage("Slice restrictions are not supported on collections containing durations",
                     "SELECT * FROM %s WHERE m > {1:1s, 3:3s} ALLOW FILTERING");
@@ -2939,8 +2957,9 @@ public class SelectTest extends CQLTester
         assertRows(execute("SELECT * FROM %s WHERE t = (1, 2s) ALLOW FILTERING"),
                    row(0, tuple(1, Duration.from("2s"))));
 
-        assertInvalidMessage("IN predicates on non-primary-key columns (t) is not yet supported",
-                "SELECT * FROM %s WHERE t IN ((1, 2s), (1, 3s)) ALLOW FILTERING");
+        assertRows(execute("SELECT * FROM %s WHERE t IN ((1, 2s), (1, 3s)) ALLOW FILTERING"),
+                   row(0, tuple(1, Duration.from("2s"))),
+                   row(2, tuple(1, Duration.from("3s"))));
 
         assertInvalidMessage("Slice restrictions are not supported on tuples containing durations",
                 "SELECT * FROM %s WHERE t > (1, 2s) ALLOW FILTERING");
@@ -2970,11 +2989,22 @@ public class SelectTest extends CQLTester
             execute("INSERT INTO %s (k, u) VALUES (2, {i: 1, d:3s})");
 
             if (frozen)
+            {
                 assertRows(execute("SELECT * FROM %s WHERE u = {i: 1, d:2s} ALLOW FILTERING"),
                            row(0, userType("i", 1, "d", Duration.from("2s"))));
 
-            assertInvalidMessage("IN predicates on non-primary-key columns (u) is not yet supported",
-                    "SELECT * FROM %s WHERE u IN ({i: 2, d:3s}, {i: 1, d:3s}) ALLOW FILTERING");
+                assertRows(execute("SELECT * FROM %s WHERE u IN ({i: 2, d:3s}, {i: 1, d:3s}) ALLOW FILTERING"),
+                        row(1, userType("i", 2, "d", Duration.from("3s"))),
+                        row(2, userType("i", 1, "d", Duration.from("3s"))));
+            }
+            else
+            {
+                assertInvalidMessage("Non-frozen UDT column 'u' (" + udt + ") cannot be restricted by any relation",
+                        "SELECT * FROM %s WHERE u = {i: 1, d:2s} ALLOW FILTERING");
+
+                assertInvalidMessage("Non-frozen UDT column 'u' (" + udt + ") cannot be restricted by any relation",
+                        "SELECT * FROM %s WHERE u IN ({i: 2, d:3s}, {i: 1, d:3s}) ALLOW FILTERING");
+            }
 
             assertInvalidMessage("Slice restrictions are not supported on UDTs containing durations",
                     "SELECT * FROM %s WHERE u > {i: 1, d:3s} ALLOW FILTERING");
diff --git a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java
index 695f040..c786184 100644
--- a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java
+++ b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.index.sasi;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.Sets;
@@ -32,8 +33,10 @@ import org.junit.Assert;
 
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.service.ClientWarn;
+import org.apache.cassandra.transport.ProtocolVersion;
 
 public class SASICQLTest extends CQLTester
 {
@@ -348,4 +351,17 @@ public class SASICQLTest extends CQLTester
             }
         }
     }
+
+    @Test
+    public void testInOperator() throws Throwable
+    {
+        createTable("CREATE TABLE %s (pk int primary key, v int);");
+
+        createIndex("CREATE CUSTOM INDEX ON %s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
+
+        assertInvalidThrowMessage(Optional.of(ProtocolVersion.CURRENT),
+                                  StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                  InvalidQueryException.class,
+                                  "SELECT * FROM %s WHERE v IN (200, 250, 300)");
+    }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org