apache/cassandra · error · InvalidRequestException

Collection column can only be restricted by CONTAINS…

Error message

Collection column %s can only be restricted by CONTAINS, CONTAINS KEY, NOT_CONTAINS, NOT_CONTAINS_KEY or map-entry equality if it already restricted by one of those

What it means

When two restrictions on the same collection column are merged, at least one must be a CONTAINS, CONTAINS KEY, NOT_CONTAINS or NOT_CONTAINS_KEY (or a map-entry equality already anchored by those). MergedRestriction.validate throws this when a second, unrelated restriction (e.g. EQ or slice) is combined with a collection restriction that does not permit it, since such filtering cannot be served by the collection indexes.

Solutions

  1. Ensure at least one restriction on the column is CONTAINS / CONTAINS KEY / NOT_CONTAINS / NOT_CONTAINS KEY (with an index) before adding further predicates
  2. Use a map-entry equality on a non-frozen map instead of full-collection comparisons
  3. Restructure the query or data model (denormalize the collection entries into rows) so each predicate targets a simple column

Example fix

// before
SELECT * FROM t WHERE tags = {'a','b'} AND size(tags) > 1; // invalid second restriction pattern
// after
SELECT * FROM t WHERE tags CONTAINS 'a' AND tags CONTAINS 'b' ALLOW FILTERING;
Defensive patterns

Strategy: validation

Validate before calling

// ensure the first restriction on a collection column is CONTAINS-family before adding more
Restriction merged = where.restrictionFor(col);
if (!(merged.isContains() || merged.isMapElementExpression()))
    throw new IllegalArgumentException("column " + col.name + " must first be restricted by CONTAINS/CONTAINS KEY");

Prevention

When it happens

Trigger: A query adding a second restriction on a collection column where neither merged side is a CONTAINS-family operator nor a valid map-entry expression — e.g. tags = {...} AND tags CONTAINS 'x' is fine, but merging two non-contains restrictions, or a slice plus EQ on a collection column, reaches this throw.

Common situations: Combining full-collection equality with other predicates in ad-hoc CQL; ORM/query-builders emitting multiple predicates on the same list/set/map column; users expecting SQL-style arbitrary column predicates.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/ad0a62b3667ab72b. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java:141

    private static void validate(SimpleRestriction restriction, SimpleRestriction other)
    {
        checkOperator(restriction);
        checkOperator(other);

        if (restriction.isContains() != other.isContains())
        {
            SimpleRestriction mapEntryRestriction = restriction.isContains() ? restriction : other;
            if (mapEntryRestriction.isMapElementExpression())
            {
                ColumnMetadata column = mapEntryRestriction.firstColumn();
                if (column.type.isFrozenCollection())
                {
                    throw invalidRequest(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, column.name);
                }
            }

            throw invalidRequest("Collection column %s can only be restricted by CONTAINS, CONTAINS KEY, NOT_CONTAINS, NOT_CONTAINS_KEY" +
                                 " or map-entry equality if it already restricted by one of those",
                                 restriction.firstColumn().name);
        }

        if (restriction.isSlice() && other.isSlice())
        {
            ColumnMetadata firstColumn = restriction.firstColumn();
            ColumnMetadata otherFirstColumn = other.firstColumn();
            if (!firstColumn.equals(otherFirstColumn))
            {
                ColumnMetadata column = firstColumn.position() > otherFirstColumn.position() ? firstColumn
                                                                                             : otherFirstColumn;

                throw invalidRequest("Column \"%s\" cannot be restricted by two inequalities not starting with the same column",
                                     column.name);
            }

            if ((restriction.operator() == Operator.GT || restriction.operator() == Operator.GTE || restriction.operator() == Operator.BETWEEN) &&

View on GitHub (pinned to 88fd0f6a0e)