risingwavelabs/risingwave · error

Type mismatched between else and case.

Error message

Type mismatched between else and case.

What it means

In `build_constant_lookup_expr`, the optional trailing ELSE clause must have the same return type as the CASE's declared return type. A mismatch would make the output column type inconsistent at runtime, so the builder rejects it during construction.

Solutions

  1. Add an explicit cast on the ELSE clause to match the CASE return type.
  2. Verify the type inference pass assigned a common return type across all THEN/ELSE arms.
  3. Check for schema drift in the columns referenced by the ELSE expression.

Example fix

// before
CASE key WHEN 1 THEN 'a' ELSE 42 END  -- else is int, case is varchar
// after
CASE key WHEN 1 THEN 'a' ELSE 42::varchar END
Defensive patterns

Strategy: validation

Validate before calling

-- assert else type matches case return type before build
-- app-side check:
if (elseExpr.returnType !== caseReturnType) addCast(elseExpr, caseReturnType);

Prevention

When it happens

Trigger: Building a constant-lookup CASE where the ELSE expression's DataType differs from the CASE return_type (e.g. ELSE returns INT when the CASE was typed as VARCHAR), typically when RETURN_TYPE coercion was skipped or types were declared incorrectly upstream.

Common situations: Hand-built expression trees; frontend type-inference bugs where implicit casts to the common type were not inserted; schema changes altering a column type used in ELSE.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/6efafeef2d059e94. Report an issue: GitHub.

Appendix: source

Thrown at src/expr/impl/src/scalar/case.rs:288

    let mut children = children;

    let operand = children.remove(0);

    let mut arms = HashMap::new();

    // Build the `arms` with iterating over `when` & `then` clauses
    let mut iter = children.into_iter().array_chunks();
    for [when, then] in iter.by_ref() {
        let Ok(Some(s)) = when.eval_const() else {
            bail!("expect when expression to be const");
        };
        arms.insert(s, then);
    }

    let fallback = if let Some(else_clause) = iter.into_remainder().next() {
        if else_clause.return_type() != return_type {
            bail!("Type mismatched between else and case.");
        }
        Some(else_clause)
    } else {
        None
    };

    let BoxedExpression::Sync(operand) = operand else {
        return Ok(ConstantLookupExpression::new(return_type, arms, fallback, operand).boxed());
    };
    let arms: Vec<_> = arms.into_iter().collect();
    let sync_arms: HashMap<ScalarImpl, Arc<dyn SyncExpression>> = match try_convert_all(
        arms,
        |(key, expr)| match expr {
            BoxedExpression::Sync(expr) => Ok((key, expr)),
            expr @ BoxedExpression::Async(_) => Err((key, expr)),
        },
        |(key, expr)| (key, BoxedExpression::Sync(expr)),
    ) {

View on GitHub (pinned to 6469eb736d)