databendlabs/databend · error

expect tuple type

Error message

expect tuple type

What it means

`ColumnOrientedSegment::col_by_name` materializes a segment-level block whose columns are nested tuples (e.g. block-level metadata encoded as tuple columns). It unwraps the outer column as a tuple and expects each selected field's declared type to also be a `TableDataType::Tuple`; when a field is not a tuple it panics with 'expect tuple type', because the lookup by dotted name (`a.b`) can only recurse into tuple sub-columns.

Solutions

  1. Verify the segment was written by a compatible table-meta schema version; re-write/compact the segment with the current version.
  2. Check the requested column name path: the first segment must refer to a tuple-typed field in the segment block schema.
  3. Replace the panic with a proper error (e.g. `ErrorCode::UnexpectedColumnCornerCase`) that names the offending field and its actual type.

Example fix

// before
_ => panic!("expect tuple type"),
// after
_ => return Err(ErrorCode::UnexpectedColumnCornerCase(format!(
    "expect tuple type for column '{}', got {:?}", name[0], field.data_type))),
Defensive patterns

Strategy: validation

Validate before calling

// Verify the requested top-level field is a tuple before lookup
fn field_is_tuple(schema: &TableSchema, name: &str) -> bool {
    matches!(schema.field_with_name(name).map(|f| &f.data_type),
             Ok(TableDataType::Tuple { .. }))
}

Type guard

fn as_tuple(dt: &TableDataType) -> Option<(&Vec<String>, &Vec<TableDataType>)> {
    if let TableDataType::Tuple { fields_name, fields_type } = dt { Some((fields_name, fields_type)) } else { None }
}

Try / catch

let block = std::panic::catch_unwind(AssertUnwindSafe(|| segment.col_by_name(name)))
    .map_err(|_| format!("column '{}' is not a nested tuple in segment", name))?;

Prevention

When it happens

Trigger: Requesting a column whose first path segment exists but whose declared type is not a Tuple — e.g. calling the helper accessors (`row_count_col`, `stat_col`, `meta_col`, etc., via `check_block_level_meta`) against a segment block whose schema was written by a different format version where the field is a plain scalar instead of a nested tuple struct.

Common situations: Reading table metadata segments written by an older/newer Databend version with a changed segment meta schema; corrupted or hand-modified segment files; custom code calling `col_by_name` with a non-tuple top-level field.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/e4b1549eebaffa78. Report an issue: GitHub.

Appendix: source

Thrown at src/query/storages/common/table_meta/src/meta/column_oriented_segment/segment.rs:213

            .unwrap()
            .as_u_int64()
            .unwrap()
            .clone()
    }

    pub fn col_by_name(&self, name: &[&str]) -> Option<Column> {
        let (index, field) = self.segment_schema.column_with_name(name[0])?;
        let column = self.block_metas.get_by_offset(index).to_column();
        if name.len() == 1 {
            Some(column)
        } else {
            let sub_cols = column.as_tuple().unwrap();
            match &field.data_type {
                TableDataType::Tuple {
                    fields_name,
                    fields_type,
                } => Self::col_by_name_inner(&name[1..], sub_cols, fields_name, fields_type),
                _ => panic!("expect tuple type"),
            }
        }
    }

    fn col_by_name_inner(
        name: &[&str],
        cols: &[Column],
        field_names: &[String],
        field_types: &[TableDataType],
    ) -> Option<Column> {
        let index = field_names.iter().position(|f| f == name[0])?;
        let column = cols[index].clone();
        if name.len() == 1 {
            Some(column)
        } else {
            let sub_cols = column.as_tuple().unwrap();
            match &field_types[index] {
                TableDataType::Tuple {

View on GitHub (pinned to 288d84d76e)