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

  1. Report the exact group_concat call shape — the accumulator must be seeded Text '' before the first step
  2. Drop ORDER BY / DISTINCT / FILTER modifiers on group_concat to use the plain path
  3. Compute the concatenation in its own GROUP BY subquery level
  4. 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

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


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/a62ae9ff97c24e87. Report an issue: GitHub.