databendlabs/databend · error
grouping function must be rewritten in type_checker, but got
Error message
grouping function must be rewritten in type_checker, but got: {:?} What it means
The `grouping` function is registered with a placeholder scalar evaluator in src/query/functions/src/scalars/other.rs:433 that must never run: the query pipeline is required to rewrite GROUPING(...) during type checking into an expression over the GROUPING ID bitmap. Reaching this evaluator means that rewrite was skipped, so the engine panics with the evaluated args for diagnostics.
Solutions
- Verify the query plan goes through the type_checker pass that rewrites GROUPING before execution.
- If building plans programmatically, apply the same rewrite pass the normal pipeline applies before calling the evaluator.
- Replace the placeholder evaluator with a real scalar implementation if GROUPING must be evaluable at scalar level.
- Report the query shape (exact SQL and plan) as a bug — this path is unreachable in normal execution.
Example fix
// before
scalar_evaluator(move |args, _| {
unreachable!("grouping function must be rewritten in type_checker, but got: {:?}", args)
})
// after
scalar_evaluator(move |args, _| {
let ids = args[0].as_number_u32().ok_or_else(||
ErrorCode::BadArguments("grouping requires rewritten UInt32 grouping-id argument".into()))?;
Ok(Value::UInt32(compute_grouping_from_ids(ids)))
}) Defensive patterns
Strategy: validation
Validate before calling
// Before execution, confirm the plan had GROUPING rewritten:
fn assert_grouping_rewritten(plan: &Plan) -> Result<(), String> {
if plan_contains_unrewritten_grouping(plan) {
Err("GROUPING function was not rewritten in type_checker".into())
} else { Ok(()) }
} Try / catch
// Wrap plan execution to detect the invariant break:
match std::panic::catch_unwind(|| execute(plan.clone())) {
Ok(r) => r,
Err(e) => return Err(ErrorCode::Internal(
format!("plan execution invariant violated: {:?}", e))),
} Prevention
- Never bypass type_checker when building or replaying plans programmatically.
- Do not cache plans across versions where the rewrite pass changed.
- Treat this panic as a bug report trigger: capture the SQL and plan tree.
When it happens
Trigger: A GROUPING(...) call bypasses type_checker rewriting — e.g. executed via an execution path that skips the rewrite pass, a cached/externally-built plan, or direct invocation of the registered scalar evaluator in tests or extensions.
Common situations: Custom planners or external plan builders that construct GROUPING function expressions without running the type_checker rewrite; regression after refactoring the rewrite pass; executing raw function calls through lower-level APIs (e.g. extensions or embedded use of the function registry).
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
- unexpected value type for grouping function
- DataExchange::Merge(_) => unreachable!()
- DataExchange::Broadcast(_) => unreachable!()
- DataExchange::GlobalShuffleExchange(_) => unreachable!()
- {}
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/d2c4a27ef12a92d2.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/functions/src/scalars/other.rs:433
derive_stat: None,
},
}))
}));
registry.register_function_factory("grouping", grouping);
// dummy grouping
// used in type_check before AggregateRewriter
let dummy_grouping = FunctionFactory::Closure(Box::new(|_, arg_type: &[DataType]| {
Some(Arc::new(Function {
signature: FunctionSignature {
name: "grouping".to_string(),
args_type: vec![DataType::Generic(0); arg_type.len()],
return_type: DataType::Number(NumberDataType::UInt32),
},
eval: FunctionEval::Scalar {
calc_domain: Box::new(FunctionDomain::Full),
eval: scalar_evaluator(move |args, _| {
unreachable!(
"grouping function must be rewritten in type_checker, but got: {:?}",
args
)
}),
derive_stat: None,
},
}))
}));
registry.register_function_factory("grouping", dummy_grouping);
}
fn register_num_to_char(registry: &mut FunctionRegistry) {
registry.register_aliases("to_string", &["to_char"]);
registry.register_passthrough_nullable_2_arg::<Int64Type, StringType, StringType, _, _>(
"to_string",
|_, _, _| FunctionDomain::MayThrow,
vectorize_with_builder_2_arg::<Int64Type, StringType, StringType>(
|value, fmt, builder, ctx| {View on GitHub (pinned to 288d84d76e)