{"record":{"id":"3f14ab1fb7ffad5c","repo":"apache/cassandra","slug":"ann-ordering-by-vector-requires-all-restricted-col","errorCode":null,"errorMessage":"ANN ordering by vector requires all restricted column(s) to be indexed","messagePattern":"ANN ordering by vector requires all restricted column\\(s\\) to be indexed","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java","lineNumber":337,"sourceCode":"                throw invalidRequest(\"Non PRIMARY KEY columns found in where clause: %s \",\n                                     Joiner.on(\", \").join(nonPrimaryKeyColumns));\n            }\n\n            Optional<SingleRestriction> annRestriction = Streams.stream(nonPrimaryKeyRestrictions)\n                                                                .filter(SingleRestriction::isANN)\n                                                                .findFirst();\n            if (annRestriction.isPresent())\n            {\n                // If there is an ANN restriction then it must be for a vector<float, n> column, and it must have an index\n                ColumnMetadata annColumn = annRestriction.get().firstColumn();\n\n                if (!annColumn.type.isVector() || !(((VectorType<?>)annColumn.type).elementType instanceof FloatType))\n                    throw invalidRequest(ANN_ONLY_SUPPORTED_ON_VECTOR_MESSAGE);\n                if (indexRegistry == null || indexRegistry.listIndexes().stream().noneMatch(i -> i.dependsOn(annColumn)))\n                    throw invalidRequest(ANN_REQUIRES_INDEX_MESSAGE);\n                // We do not allow ANN queries using partition key restrictions that need filtering\n                if (partitionKeyRestrictions.needFiltering())\n                    throw invalidRequest(ANN_REQUIRES_INDEXED_FILTERING_MESSAGE);\n                // We do not allow ANN query filtering using non-indexed columns\n                List<ColumnMetadata> nonAnnColumns = Streams.stream(nonPrimaryKeyRestrictions)\n                                                            .filter(r -> !r.isANN())\n                                                            .map(SingleRestriction::firstColumn)\n                                                            .collect(Collectors.toList());\n                List<ColumnMetadata> clusteringColumns = clusteringColumnsRestrictions.columns();\n                if (!nonAnnColumns.isEmpty() || !clusteringColumns.isEmpty())\n                {\n                    List<ColumnMetadata> nonIndexedColumns = Stream.concat(nonAnnColumns.stream(), clusteringColumns.stream())\n                                                                   .filter(c -> indexRegistry.listIndexes().stream().noneMatch(i -> i.dependsOn(c)))\n                                                                   .collect(Collectors.toList());\n                    if (!nonIndexedColumns.isEmpty())\n                    {\n                        // restrictions on non-clustering columns, or clusterings that still need filtering, are invalid\n                        if (!clusteringColumns.containsAll(nonIndexedColumns)\n                                || partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents()\n                                || clusteringColumnsRestrictions.needFiltering())\n                            throw invalidRequest(StatementRestrictions.ANN_REQUIRES_INDEXED_FILTERING_MESSAGE);","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java#L319-L355","documentation":"Thrown when an ANN query combines the ANN ordering with additional filtering (non-ANN restrictions on other columns) where at least one filtered column lacks a supporting index. Cassandra requires every non-ANN restriction in an ANN query to be served by an index so the filtering happens in the indexed/ANN path, not as a post-filter scan.","triggerScenarios":"`SELECT * FROM t WHERE cat = 'a' ORDER BY v ANN OF [...]` where v is indexed but cat is not; ANN query with clustering/partition filtering columns that are not indexed; combining ANN with several predicates, some unindexed.","commonSituations":"Hybrid vector+metadata search where metadata columns were never indexed; indexes exist for some but not all filter columns; adding new filter columns to an existing ANN query without indexing them.","solutions":["Create indexes on every non-ANN column used in the WHERE clause of the ANN query.","Remove the unindexed filter predicates and post-filter results client-side after the ANN search.","Ensure partition key restrictions are direct equality (not filtering) and clustering filters are indexed.","Denormalize the data so metadata filters become partition-key equalities in a query table."],"exampleFix":"// before\nSELECT * FROM t WHERE cat = 'a' ORDER BY v ANN OF [0.1,0.2]; -- cat not indexed\n// after\nCREATE INDEX ON t (cat);\nSELECT * FROM t WHERE cat = 'a' ORDER BY v ANN OF [0.1,0.2];","handlingStrategy":"validation","validationCode":"// every non-ANN WHERE column in an ANN query must be indexed\nList<String> nonAnn = whereColumns.stream().filter(c -> !c.equals(annColumn)).collect(toList());\nfor (String c : nonAnn)\n    if (!isIndexed(table, c)) throw new IllegalStateException(\"ANN query filter column not indexed: \" + c);","typeGuard":null,"tryCatchPattern":"try { session.execute(query); } catch (InvalidRequestException e) { if (e.getMessage().contains(\"all restricted column(s) to be indexed\")) postFilterClientSide(); else throw e; }","preventionTips":["Index all metadata columns used alongside ANN filters at schema creation.","Prefer partition-key equality over filter columns for hybrid vector search.","Review any new filter added to ANN queries for index coverage."],"tags":["cql","ann","vector-index","filtering"],"backgroundTag":"missing-required-index","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}