cube-js/cube · error

Unexpected join node: {:?}

Error message

Unexpected join node: {:?}

What it means

to_logical_plan builds joins from select IR nodes; each join input must be a recognized join-language node. When a join operand node has an unexpected shape, the converter panics with 'Unexpected join node' and the debug dump of the node.

Source

Thrown at rust/cubesql/cubesql/src/compile/rewrite/converter.rs:2193

                        .collect::<Result<Vec<_>, CubeError>>()?;
                let group_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[3], WrappedSelectGroupExpr);
                let aggr_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[4], WrappedSelectAggrExpr);
                let window_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[5], WrappedSelectWindowExpr);
                let from = Arc::new(self.to_logical_plan(params[6])?);
                let joins = match_list_node!(node_by_id, params[7], WrappedSelectJoins)
                    .into_iter()
                    .map(|j| {
                        if let LogicalPlanLanguage::WrappedSelectJoin(params) = j {
                            let input = Arc::new(self.to_logical_plan(params[0])?);
                            let join_expr = to_expr(params[1])?;
                            let join_type =
                                match_data_node!(node_by_id, params[2], WrappedSelectJoinJoinType);
                            Ok((input, join_expr, join_type))
                        } else {
                            panic!("Unexpected join node: {:?}", j)
                        }
                    })
                    .collect::<Result<Vec<_>, _>>()?;

                let filter_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[8], WrappedSelectFilterExpr);
                let having_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[9], WrappedSelectHavingExpr);
                let limit = match_data_node!(node_by_id, params[10], WrappedSelectLimit);
                let offset = match_data_node!(node_by_id, params[11], WrappedSelectOffset);
                let order_expr =
                    match_expr_list_node!(node_by_id, to_expr, params[12], WrappedSelectOrderExpr);
                let alias = match_data_node!(node_by_id, params[13], WrappedSelectAlias);
                let distinct = match_data_node!(node_by_id, params[14], WrappedSelectDistinct);
                let push_to_cube =
                    match_data_node!(node_by_id, params[15], WrappedSelectPushToCube);

                let filter_expr = normalize_cols(

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Rewrite the join using standard INNER/LEFT joins with simple ON conditions
  2. Replace subquery-in-join patterns with CTEs and join those
  3. Upgrade CubeSQL; if reproducible with plain SQL, file an issue with the dumped node

Example fix

// before
SELECT ... FROM a, LATERAL (SELECT ...) b
// after
WITH b AS (SELECT ...) SELECT ... FROM a JOIN b ON ...
Defensive patterns

Strategy: try-catch

Validate before calling

// Reject join shapes known to fail conversion before sending
fn validate_joins(stmt: &SqlStatement) -> Result<(), String> {
    for j in stmt.joins() {
        if matches!(j.kind, JoinKind::Lateral | JoinKind::Asymmetric | JoinKind::CrossApply) {
            return Err(format!("join kind {:?} may not be supported", j.kind));
        }
        if j.on_condition().contains_subquery() { return Err("subqueries in ON clauses are unsupported".into()); }
    }
    Ok(())
}

Try / catch

let res = std::panic::catch_unwind(AssertUnwindSafe(|| planner.plan(sql)));
if res.is_err() {
    // surface 'Unexpected join node' as a 4xx with guidance to simplify the join
    return bad_request("Query uses a join shape the planner cannot convert; rewrite with standard INNER/LEFT joins");
}

Prevention

When it happens

Trigger: A SELECT with a JOIN whose operand (input, join expression, or join type) doesn't match the expected LogicalPlanLanguage variants — e.g. exotic join types (lateral, asymmetric), complex join conditions, or joins on unsupported constructs.

Common situations: SQL API queries using JOIN types/conditions CubeSQL's planner can't model (FULL OUTER with complex ON, LATERAL joins, subquery joins in unusual positions); older drivers emitting legacy join syntax.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/178086aa745c0f90. Report an issue: GitHub.