{"record":{"id":"797cecf790ddcfd6","repo":"apache/cassandra","slug":"all-indexed-columns-should-be-included-into-the-co","errorCode":null,"errorMessage":"All indexed columns should be included into the column slice, missing: ${column}","messagePattern":"All indexed columns should be included into the column slice, missing: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/index/sasi/plan/Operation.java","lineNumber":223,"sourceCode":"    private boolean localSatisfiedBy(Unfiltered currentCluster, Row staticRow, boolean allowMissingColumns)\n    {\n        if (currentCluster == null || !currentCluster.isRow())\n            return false;\n\n        final long now = FBUtilities.nowInSeconds();\n        boolean result = false;\n        int idx = 0;\n\n        for (ColumnMetadata column : expressions.keySet())\n        {\n            if (column.kind == Kind.PARTITION_KEY)\n                continue;\n\n            ByteBuffer value = ColumnIndex.getValueOf(column, column.kind == Kind.STATIC ? staticRow : (Row) currentCluster, now);\n            boolean isMissingColumn = value == null;\n\n            if (!allowMissingColumns && isMissingColumn)\n                throw new IllegalStateException(\"All indexed columns should be included into the column slice, missing: \" + column);\n\n            boolean isMatch = false;\n            // If there is a column with multiple expressions that effectively means an OR\n            // e.g. comment = 'x y z' could be split into 'comment' EQ 'x', 'comment' EQ 'y', 'comment' EQ 'z'\n            // by analyzer, in situation like that we only need to check if at least one of expressions matches,\n            // and there is no hit on the NOT_EQ (if any) which are always at the end of the filter list.\n            // Loop always starts from the end of the list, which makes it possible to break after the last\n            // NOT_EQ condition on first EQ/RANGE condition satisfied, instead of checking every\n            // single expression in the column filter list.\n            List<Expression> filters = expressions.get(column);\n            for (int i = filters.size() - 1; i >= 0; i--)\n            {\n                Expression expression = filters.get(i);\n                isMatch = !isMissingColumn && expression.isSatisfiedBy(value);\n                if (expression.getOp() == Op.NOT_EQ)\n                {\n                    // since this is NOT_EQ operation we have to\n                    // inverse match flag (to check against other expressions),","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/index/sasi/plan/Operation.java#L205-L241","documentation":"Operation.localSatisfiedBy evaluates each indexed column of a column slice against a row. When allowMissingColumns is false, any indexed column whose value cannot be resolved (null from ColumnIndex.getValueOf) is treated as a broken plan invariant and throws IllegalStateException. The library assumes query planning guaranteed every indexed column is present in the slice being evaluated.","triggerScenarios":"Executing a SASI query whose plan includes multiple indexed columns where a row (or the static row for Kind.STATIC columns) is missing one of the indexed column values; the controller built the Operation without allowing missing columns, then satisfiedBy() reaches a row lacking a column value.","commonSituations":"Rows written before a column was added to the schema but after the SASI index was created; queries mixing regular and static indexed columns; legacy SSTables that don't contain the indexed column; schema disagreements between nodes.","solutions":["Run a full repair and rebuild the SASI index so all SSTables/rows contain the indexed column values","Check schema agreement (SELECT * FROM system_schema.columns) and ensure all nodes agree the indexed column exists","Ensure rows are rewritten (UPDATE setting the column) if pre-existing data predates the column addition","If the query is intentionally tolerant of missing columns, verify the allowMissingColumns flag is set by the planner path you use"],"exampleFix":"// before\ncqlsh> SELECT * FROM ks.tbl WHERE age = 30 ALLOW FILTERING; // rows missing 'age' crash SASI plan eval\n// after\ncqlsh> ALTER TABLE ks.tbl DROP INDEX age_idx;\ncqlsh> ALTER TABLE ks.tbl ADD age int; -- ensure column exists before indexing\ncqlsh> CREATE CUSTOM INDEX age_idx ON ks.tbl (age) USING 'org.apache.cassandra.index.sasi.SASIIndex';","handlingStrategy":"try-catch","validationCode":"cqlsh> SELECT column_name FROM system_schema.columns WHERE keyspace_name='ks' AND table_name='tbl';\n-- confirm the indexed column exists and is populated in all rows before querying","typeGuard":null,"tryCatchPattern":"try {\n    session.execute(\"SELECT * FROM ks.tbl WHERE age = ?\", 30);\n} catch (IllegalStateException e) {\n    // log plan inconsistency; trigger repair/index rebuild\n    scheduler.rebuildIndex(\"ks\", \"tbl\");\n}","preventionTips":["Add columns before creating SASI indexes on them; backfill values for existing rows first","Keep the schema in agreement across nodes; check schema versions after DDL","Run regular repairs so SSTables from different epochs stay consistent"],"tags":["sasi","query-planning","invariant","schema"],"backgroundTag":"internal-invariant-violation","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"}