{"record":{"id":"57c8f6a6576db2af","repo":"apache/cassandra","slug":"cannot-create-index-on-non-frozen-udt-column-s","errorCode":null,"errorMessage":"Cannot create index on non-frozen UDT column %s","messagePattern":"Cannot create index on non-frozen UDT column (.+?)","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java","lineNumber":315,"sourceCode":"        // Frozen collections are only supported with SAI indexes.\n        if (isNonSAIIndex && baseType.isCollection() && !column.type.isMultiCell())\n        {\n            if (target.type == Type.VALUES || target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES)\n            {\n                throw ire(CREATE_ON_FROZEN_COLUMN, target.type.toString(), column.name, column.name);\n            }\n        }\n\n        if (!(baseType instanceof MapType) && (target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES ))\n            throw ire(CREATE_WITH_NON_MAP_TYPE, target.type, column);\n\n        // Can't query map[key]=value on clustering key columns, so ENTRIES index would be not queryable.\n        if (column.isClusteringColumn() && baseType instanceof MapType && !column.type.isMultiCell()\n            && target.type == Type.KEYS_AND_VALUES)\n            throw ire(ENTRIES_INDEX_ON_FROZEN_MAP_CLUSTERING_KEY_NOT_SUPPORTED, column.name);\n\n        if (column.type.isUDT() && column.type.isMultiCell())\n            throw ire(CREATE_ON_NON_FROZEN_UDT, column);\n    }\n\n    /**\n     * Checks if the given index attributes represent a Storage Attached Index.\n     */\n    private boolean isSAIIndex(IndexAttributes attrs)\n    {\n        return attrs.isCustom && IndexMetadata.isSAIIndex(attrs.customClass);\n    }\n\n    private String generateIndexName(KeyspaceMetadata keyspace, List<IndexTarget> targets)\n    {\n        String baseName = targets.size() == 1\n                        ? IndexMetadata.generateDefaultIndexName(tableName, targets.get(0).column)\n                        : IndexMetadata.generateDefaultIndexName(tableName);\n        return keyspace.findAvailableIndexName(baseName);\n    }\n","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java#L297-L333","documentation":"Cassandra rejects CREATE INDEX when the target column is a user-defined type (UDT) that is not frozen (i.e. isMultiCell). Non-frozen UDTs store each field in separate cells, so a plain index over the whole column cannot be built; only frozen UDT columns (or indexing specific non-frozen UDT sub-fields) are supported.","triggerScenarios":"CREATE INDEX ... ON table(udt_column) where udt_column is declared as a multi-cell (non-frozen) UDT, e.g. a column typed `address <address_udt>` without the frozen<> wrapper. Raised from validateIndexTarget during CREATE INDEX statement validation.","commonSituations":"Developers migrate a schema from frozen to non-frozen UDTs to get partial updates, then forget existing secondary indexes are no longer valid; or they model a UDT column and try to index it directly like a scalar column.","solutions":["Declare the column frozen in the table schema: `address frozen<address_udt>`, then create the index.","Index a specific non-frozen UDT field instead of the whole column: CREATE INDEX ON t(keys(addr)) or ON t(addr.zip) style UDT-field targets if supported.","Use Storage Attached Index (SAI) via `CREATE CUSTOM INDEX ... USING 'StorageAttachedIndex'` if the field-level indexing needed is supported.","Re-model the data: split the UDT fields into regular columns that can each be indexed."],"exampleFix":"// before\nCREATE TABLE t (id int PRIMARY KEY, addr address_udt);\nCREATE INDEX ON t(addr);\n\n// after\nCREATE TABLE t (id int PRIMARY KEY, addr frozen<address_udt>);\nCREATE INDEX ON t(addr);","handlingStrategy":"validation","validationCode":"if (columnType.startsWith(\"frozen<\")) { /* indexable */ } else if (isUdt(columnType)) throw new IllegalArgumentException(\"Declare column as frozen<udt> before indexing: \" + columnName);","typeGuard":"boolean isIndexableUdt(String colType) { return colType != null && colType.matches(\"frozen<.*>\"); }","tryCatchPattern":"try { session.execute(createIndexCql); } catch (InvalidQueryException e) { if (e.getMessage().contains(\"non-frozen UDT\")) { /* alter schema to frozen<> or index a UDT field */ } else throw e; }","preventionTips":["Prefer frozen<udt> for columns you intend to index","Keep schema DDL and index DDL in the same migration so wrappers are not lost","Check system_schema.columns type strings before generating index DDL"],"tags":["cassandra","cql","index","udt"],"backgroundTag":"schema-validation-failed","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"}