tursodatabase/turso · error
group_concat accumulator must be a Text value
Error message
group_concat accumulator must be a Text value
What it means
Value::exec_group_concat() appends a row's value to the group accumulator and requires the accumulator to already be Value::Text — SQLite seeds group_concat's accumulator with an empty string before the first step. The panic means the accumulator held a non-Text value (Null, Integer, ...) when the next row was concatenated: the aggregate's initial value was never set up, or the accumulator register was clobbered or reused by other bytecode.
Source
Thrown at core/vdbe/value.rs:1420
return Value::Null;
}
result = Some(match result {
None => v,
Some(cur) if v > cur => v,
Some(cur) => cur,
});
}
result.map(|v| v.to_owned()).unwrap_or(Value::Null)
}
/// Fallibly concatenate another value onto this Text value, converting it to a string.
/// Panics if self is not a Text value.
pub fn exec_group_concat(
&mut self,
other: &Value,
) -> std::result::Result<(), crate::alloc::TryReserveError> {
let Value::Text(text) = self else {
panic!("group_concat accumulator must be a Text value");
};
let acc = match &mut text.value {
std::borrow::Cow::Owned(s) => s,
borrowed => {
let mut s = String::new();
s.try_reserve(borrowed.len())?;
s.push_str(borrowed);
*borrowed = std::borrow::Cow::Owned(s);
let std::borrow::Cow::Owned(s) = borrowed else {
unreachable!("accumulator was just converted to Owned");
};
s
}
};
match other {
Value::Text(text) => {
acc.try_reserve(text.as_str().len())?;
acc.push_str(text.as_str());View on GitHub (pinned to 492c4a71cd)
Solutions
- Report the exact group_concat call shape — the accumulator must be seeded Text '' before the first step
- Drop ORDER BY / DISTINCT / FILTER modifiers on group_concat to use the plain path
- Compute the concatenation in its own GROUP BY subquery level
- Upgrade
Example fix
-- before SELECT group_concat(v ORDER BY k) FROM t; -- after (plain form) SELECT group_concat(v) FROM t;
Defensive patterns
Strategy: type-guard
Validate before calling
// before appending a row, confirm the accumulator is seeded Text
if !matches!(accumulator, Value::Text(_)) {
let seeded = Value::Text(String::new().into()); // SQLite seeds '' before the first step
} Type guard
fn text_accumulator(v: &Value) -> bool {
matches!(v, Value::Text(_))
} Prevention
- Cover group_concat with DISTINCT/ORDER BY/FILTER in the aggregate test suite
- Ensure accumulator registers are seeded before the first AggStep in new aggregate codegen
- Report the exact call shape; the engine must seed the accumulator itself
When it happens
Trigger: SELECT group_concat(x [, sep]) where the seeded '' accumulator is lost before AggStep runs — e.g. combined with DISTINCT, ORDER BY inside group_concat, FILTER clauses, or window-function variants that re-initialize accumulators; also after changes to aggregate initialization in translate/.
Common situations: group_concat with ORDER BY or DISTINCT modifiers; aggregates in trigger or co-routine contexts; engine upgrades altering accumulator setup.
Related errors
- register holds unexpected value: {self:?}
- OpenRead on pseudo cursor
- Rewind on non-btree/materialized-view cursor
- unexpected cursor type
- Next on non-btree/materialized-view cursor
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/a62ae9ff97c24e87.
Report an issue: GitHub.