risingwavelabs/risingwave · error

Column order mismatch at position {}: the sink has column `{

Error message

Column order mismatch at position {}: the sink has column `{}` but the Iceberg table has column `{}`. The Iceberg sink maps columns to the table by position, so the sink's column order must match the Iceberg table columns [{}].

What it means

The Iceberg sink writes columns to the table by position, so `try_matches_arrow_schema` zips RW and Iceberg fields and requires names to match at each index. If positionally aligned columns have different names, it bails with a detailed message listing the position, both column names, and the full Iceberg column list.

Source

Thrown at src/connector/src/sink/iceberg/create_table.rs:424

    let mut schema_fields = HashMap::new();
    rw_schema.fields.iter().for_each(|field| {
        let res = schema_fields.insert(field.name.as_str(), &field.data_type);
        // This assert is to make sure there is no duplicate field name in the schema.
        assert!(res.is_none())
    });

    check_compatibility(schema_fields, &arrow_schema.fields)?;

    // The sink writes columns to the Iceberg table by position, so the column order
    // must match. The check above only validates the name set and types.
    for (idx, (rw_field, arrow_field)) in rw_schema
        .fields
        .iter()
        .zip_eq_fast(arrow_schema.fields().iter())
        .enumerate()
    {
        if rw_field.name.as_str() != arrow_field.name().as_str() {
            bail!(
                "Column order mismatch at position {}: the sink has column `{}` but the \
                 Iceberg table has column `{}`. The Iceberg sink maps columns to the table \
                 by position, so the sink's column order must match the Iceberg table \
                 columns [{}].",
                idx,
                rw_field.name,
                arrow_field.name(),
                arrow_schema.fields().iter().map(|f| f.name()).join(", "),
            );
        }
    }

    Ok(())
}

pub fn parse_partition_by_exprs(
    expr: String,
) -> std::result::Result<Vec<(String, Transform)>, anyhow::Error> {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Reorder the sink query's SELECT list to exactly match the Iceberg table's column order.
  2. Recreate the sink (or the table) so both schemas are generated in the same order.
  3. Check the full printed Iceberg column list in the message and align one-to-one positionally.

Example fix

// before: table is (a, b), sink selects (b, a)
CREATE SINK s AS SELECT b, a FROM t;
// after
CREATE SINK s AS SELECT a, b FROM t;
Defensive patterns

Strategy: validation

Validate before calling

// verify positional names match before creating
for (i, (rw, arrow)) in rw_schema.fields.iter().zip(arrow_schema.fields()).enumerate() {
    if rw.name != arrow.name() {
        eprintln!("position {i}: sink `{}` vs table `{}`", rw.name, arrow.name());
    }
}

Prevention

When it happens

Trigger: Called from `create_and_validate_table_impl`: e.g., sink schema is (a, b) but Iceberg table is (b, a) — same set, wrong order.

Common situations: Sink query column order differs from the table's column order; Iceberg table was created from a different column order or evolved by inserting a column; renaming a column in the table externally.

Related errors


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