risingwavelabs/risingwave · critical

should already checked index referencing column still exists

Error message

should already checked index referencing column still exists

What it means

When rewriting an index after ALTER TABLE ADD/DROP COLUMN, rewrite_expr maps an old column index to the new column list by matching column_id. The expect asserts a prior check already verified the referenced column still exists; panicking means an index references a column that was dropped or the column-id mapping is inconsistent.

Solutions

  1. Verify the index does not reference the column being dropped before issuing ALTER TABLE DROP COLUMN (the check that normally prevents this).
  2. Re-create the index without the dropped column, or drop the index first.
  3. If column ids are inconsistent in the meta store, restore from backup / rebuild the table and index.
Defensive patterns

Strategy: validation

Validate before calling

// Before DROP COLUMN, check no index item references it
if indexes_reference_column(table_id, column_id) {
    bail!("cannot drop column referenced by an index");
}

Try / catch

std::panic::catch_unwind(|| rewrite_expr(expr, &ctx))

Prevention

When it happens

Trigger: Renaming/rewriting a table whose index items reference a column absent from new_columns — i.e. the earlier 'index references dropped column' validation was skipped, bypassed, or the column-id bookkeeping is wrong (e.g. after concurrent ALTERs).

Common situations: Dropping a column that an index depends on without the pre-check firing, schema corruption after failed ALTER, concurrent DDL races.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/9314ec507a7dbd70. Report an issue: GitHub.

Appendix: source

Thrown at src/meta/src/controller/rename.rs:465

/// Rewrite the expression in index item after there's a schema change on the primary table.
// TODO: move this out of `rename.rs`, this has nothing to do with renaming.
pub struct IndexItemRewriter {
    pub original_columns: Vec<PbColumnDesc>,
    pub new_columns: Vec<PbColumnDesc>,
}

impl IndexItemRewriter {
    pub fn rewrite_expr(&self, expr: &mut ExprNode) {
        let rex_node = expr.rex_node.as_mut().unwrap();
        match rex_node {
            RexNode::InputRef(idx) => {
                let old_idx = *idx as usize;
                let original_column = &self.original_columns[old_idx];
                let (new_idx, new_column) = self
                    .new_columns
                    .iter()
                    .find_position(|c| c.column_id == original_column.column_id)
                    .expect("should already checked index referencing column still exists");
                *idx = new_idx as u32;

                // If there's a type change, we need to wrap it with an internal `CompositeCast` to
                // maintain the correct return type. It cannot execute and will be eliminated in
                // the frontend when rebuilding the index items.
                if new_column.column_type != original_column.column_type {
                    let old_type = original_column.column_type.clone().unwrap();
                    let new_type = new_column.column_type.clone().unwrap();

                    assert_eq!(&old_type, expr.return_type.as_ref().unwrap());
                    expr.return_type = Some(new_type); // update return type of `InputRef`

                    let new_expr_node = ExprNode {
                        function_type: expr_node::Type::CompositeCast as _,
                        return_type: Some(old_type),
                        rex_node: RexNode::FuncCall(FunctionCall {
                            children: vec![expr.clone()],
                        })

View on GitHub (pinned to 6469eb736d)