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

  1. Verify the query plan goes through the type_checker pass that rewrites GROUPING before execution.
  2. If building plans programmatically, apply the same rewrite pass the normal pipeline applies before calling the evaluator.
  3. Replace the placeholder evaluator with a real scalar implementation if GROUPING must be evaluable at scalar level.
  4. 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

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


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)