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
- Verify the segment was written by a compatible table-meta schema version; re-write/compact the segment with the current version.
- Check the requested column name path: the first segment must refer to a tuple-typed field in the segment block schema.
- 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
- Keep segment metadata schemas versioned and check the version before reading block-level meta columns.
- Validate dotted column paths against the schema before descending.
- Never manually edit segment files; regenerate them via compaction.
- Prefer returning typed errors over panics in library code reachable from readers.
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
- internal error: entered unreachable code
- internal error: entered unreachable code
- internal error: entered unreachable code
- {}
- Temp table id used up
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)