risingwavelabs/risingwave · error
column "{}" cannot be altered; only types containing struct
Error message
column "{}" cannot be altered; only types containing struct can be altered What it means
`ColIdGenerator::generate` rejects altering a column whose data type returns `None` from `can_alter()`: only types containing struct are supported for in-place type alteration. Altering e.g. an INT to VARCHAR (or any non-struct-containing change) is not implemented and is explicitly rejected.
Source
Thrown at src/frontend/src/handler/create_table/col_id_gen.rs:173
pub fn generate(&mut self, col: &mut ColumnCatalog) -> Result<()> {
let mut path = vec![Segment::Field(col.name().to_owned())];
if let Some((original_column_id, original_data_type)) = self.existing.get(&path) {
if original_data_type == col.data_type() {
col.column_desc.column_id = *original_column_id;
// Equality above ignores nested field IDs. We need to clone them below.
col.column_desc.data_type = original_data_type.clone();
return Ok(());
} else {
// Check if the column can be altered.
match original_data_type.can_alter() {
Some(true) => { /* pass */ }
Some(false) => bail!(
"column \"{}\" was persisted with legacy encoding thus cannot be altered, \
consider dropping and readding the column",
col.name()
),
None => bail!(
"column \"{}\" cannot be altered; only types containing struct can be altered",
col.name()
),
}
}
}
fn handle(
this: &mut ColumnIdGenerator,
path: &mut Path,
data_type: DataType,
) -> Result<(ColumnId, DataType)> {
macro_rules! with_segment {
($segment:expr, $block:block) => {{
path.push($segment);
let ret = $block;
path.pop();
retView on GitHub (pinned to 6469eb736d)
Solutions
- Drop and re-add the column with the new type, backfilling data explicitly (with casts) if needed.
- Create a new column, copy casted values, drop the old one, then rename.
- Check RisingWave docs for the supported ALTER COLUMN TYPE matrix (struct-containing types only).
Example fix
-- before (unsupported) ALTER TABLE t ALTER COLUMN qty TYPE bigint; -- after ALTER TABLE t ADD COLUMN qty_big bigint; UPDATE t SET qty_big = qty::bigint; ALTER TABLE t DROP COLUMN qty; ALTER TABLE t RENAME COLUMN qty_big TO qty;
Defensive patterns
Strategy: validation
Validate before calling
if original.can_alter().is_none() {
// plan drop+readd instead of ALTER COLUMN TYPE
return Err(format!("column '{}' type change not supported; use drop+readd", col.name()));
} Prevention
- Only attempt ALTER COLUMN TYPE when original or new type contains struct
- Generate migrations that use add/copy/drop/rename for primitive type changes
- Consult supported-type matrix in docs
When it happens
Trigger: `ALTER TABLE ... ALTER COLUMN c TYPE <new_type>` where the original or new type contains no struct — e.g. changing an `int` column to `varchar`, or `varchar` to `int`.
Common situations: Users expecting general type-change support like in OLTP databases; migration scripts generated for Postgres applied to RisingWave.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- duplicate path: {:?}
- version field not set
- column "{}" was persisted with legacy encoding thus cannot b
- incompatible data type change from {:?} to {:?} at path "{}"
- Join key types are not aligned: LHS: {outer_type:?}, RHS: {i
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/862f4d3591919b71.
Report an issue: GitHub.