databendlabs/databend · error
failed to build jsonb object from tuple
Error message
failed to build jsonb object from tuple
What it means
This panic comes from an `expect` on `OwnedJsonb::build_object` while converting a Tuple scalar into a JSONB variant during `cast_scalar_to_variant`. The library assumes that building a JSON object from the tuple's field names and values cannot fail; the expect is an internal invariant check. If it fires, the jsonb builder rejected the object construction (e.g. an unencodable raw value or an internal builder error).
Solutions
- Verify the tuple type's field names length matches the number of values passed to `cast_scalar_to_variant` (fields_name.len() == values.len()).
- Re-check the source block's declared tuple schema against the actual column schema; refresh any cached schema/metas.
- Reproduce with the specific tuple value and report to Databend with the value and cast path (this is an intended-to-be-impossible path).
- As a stopgap, replace the `expect` with proper error propagation (`map_err(ErrorCode::Internal)`) in a patched build to get a diagnosable error instead of a panic.
Example fix
// before
OwnedJsonb::build_object(...).expect("failed to build jsonb object from tuple")
// after
OwnedJsonb::build_object(...).map_err(|e| ErrorCode::Internal(format!("failed to build jsonb object from tuple: {e}")))? Defensive patterns
Strategy: validation
Validate before calling
if fields_name.len() != values.len() {
return Err(ErrorCode::Internal(format!(
"tuple field names ({}) != values ({})",
fields_name.len(), values.len()
)));
} Type guard
fn tuple_fields_match(names: &[String], values: &[ScalarRef]) -> bool { names.len() == values.len() } Prevention
- Keep tuple type field names in sync with column schema after ALTERs
- Avoid mixing Databend versions across a cluster
- Cast unknown tuple payloads with explicit schema checks before VARIANT conversion
When it happens
Trigger: Calling `cast_scalar_to_variant` on a `ScalarRef::Tuple` whose field count mismatches `fields_name` length (index out of range while mapping names), or where one of the field byte buffers cannot be embedded by `OwnedJsonb::build_object`. Reached via array_construct/object-style casts or `merge_result` aggregation of tuple values.
Common situations: Schema drift where a table's tuple type was altered but cached field names were not refreshed; mixing Databend versions where tuple encoding changed; corrupt or hand-crafted block data whose tuple payload does not match its declared type.
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
- internal error: entered unreachable code
- failed to parse geojson to json value
- {}
- Temp table id used up
- Invalid temp table desc
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/654721c101a11e82.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/expression/src/types/variant.rs:377
let iter = fields.into_iter();
let mut builder = BinaryColumnBuilder::with_capacity(iter.size_hint().0, 0);
for (scalar, typ) in iter.zip(fields_type) {
cast_scalar_to_variant(
scalar,
tz,
&mut builder.data,
Some(&typ.remove_nullable()),
);
builder.commit_row();
}
let values = builder.build();
OwnedJsonb::build_object(
values
.iter()
.enumerate()
.map(|(i, bytes)| (fields_name[i].clone(), RawJsonb::new(bytes))),
)
.expect("failed to build jsonb object from tuple")
}
_ => {
let values = cast_scalars_to_variants(fields, tz, None);
OwnedJsonb::build_object(
values
.iter()
.enumerate()
.map(|(i, bytes)| (format!("{}", i + 1), RawJsonb::new(bytes))),
)
.expect("failed to build jsonb object from tuple")
}
};
buf.extend_from_slice(owned_jsonb.as_ref());
return;
}
ScalarRef::Variant(bytes) => {
buf.extend_from_slice(bytes);
return;View on GitHub (pinned to 288d84d76e)