{"record":{"id":"30a4d6398d12d04d","repo":"apache/cassandra","slug":"a-storage-attached-index-cannot-be-created-over-mu","errorCode":null,"errorMessage":"A storage-attached index cannot be created over multiple columns: ","messagePattern":"A storage-attached index cannot be created over multiple columns: ","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java","lineNumber":255,"sourceCode":"        {\n            return unknown;\n        }\n\n        if (ILLEGAL_PARTITIONERS.contains(metadata.partitioner.getClass()))\n        {\n            throw new InvalidRequestException(\"Storage-attached index does not support the following IPartitioner implementations: \" + ILLEGAL_PARTITIONERS);\n        }\n\n        String targetColumn = options.get(IndexTarget.TARGET_OPTION_NAME);\n\n        if (targetColumn == null)\n        {\n            throw new InvalidRequestException(\"Missing target column\");\n        }\n\n        if (targetColumn.split(\",\").length > 1)\n        {\n            throw new InvalidRequestException(\"A storage-attached index cannot be created over multiple columns: \" + targetColumn);\n        }\n\n        Pair<ColumnMetadata, IndexTarget.Type> target = TargetParser.parse(metadata, targetColumn);\n\n        if (target == null)\n        {\n            throw new InvalidRequestException(\"Failed to retrieve target column for: \" + targetColumn);\n        }\n\n        // In order to support different index targets on non-frozen map, ie. KEYS, VALUE, ENTRIES, we need to put index\n        // name as part of index file name instead of column name. We only need to check that the target is different\n        // between indexes. This will only allow indexes in the same column with a different IndexTarget.Type.\n        //\n        // Note that: \"metadata.indexes\" already includes current index\n        if (metadata.indexes.stream().filter(index -> index.getIndexClassName().equals(StorageAttachedIndex.class.getName()))\n                            .map(index -> TargetParser.parse(metadata, index.options.get(IndexTarget.TARGET_OPTION_NAME)))\n                            .filter(Objects::nonNull).filter(t -> t.equals(target)).count() > 1)\n        {","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java#L237-L273","documentation":"SAI supports indexing exactly one column per index; validateOptions throws this InvalidRequestException when the target string splits on ',' into more than one column. Multi-column indexing must be expressed as multiple separate SAI indexes.","triggerScenarios":"CREATE CUSTOM INDEX ON ks.tbl (col1, col2) USING 'StorageAttachedIndex' — the driver/grammar collapses both columns into one target string; or programmatic target like \"a,b\".","commonSituations":"Porting a multi-column legacy index definition to SAI; expecting SAI to support composite/multi-column indexes like some other engines.","solutions":["Create one SAI index per column","Drop the multi-column target and split the statement","If a composite is truly needed, query with multiple single-column SAI index predicates"],"exampleFix":"// before\nCREATE CUSTOM INDEX idx ON ks.tbl (first_name, last_name) USING 'StorageAttachedIndex';\n// after\nCREATE CUSTOM INDEX idx_fn ON ks.tbl (first_name) USING 'StorageAttachedIndex';\nCREATE CUSTOM INDEX idx_ln ON ks.tbl (last_name) USING 'StorageAttachedIndex';","handlingStrategy":"validation","validationCode":"if (targetColumn.split(\",\").length > 1) throw new IllegalArgumentException(\"SAI supports one column per index: \" + targetColumn);","typeGuard":null,"tryCatchPattern":"try { session.execute(ddl); } catch (InvalidRequestException e) { if (e.getMessage().contains(\"multiple columns\")) { /* split into per-column indexes */ } else throw e; }","preventionTips":["One SAI index per column; combine predicates at query time","Do not port multi-column legacy index definitions directly to SAI"],"tags":["cassandra","sai","invalid-request","multi-column"],"backgroundTag":"unsupported-operation","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}