{"record":{"id":"c1a43845764bd0e8","repo":"risingwavelabs/risingwave","slug":"only-inner-join-without-non-equal-condition-is-sup","errorCode":null,"errorMessage":"only inner join without non-equal condition is supported for delta joins","messagePattern":"only inner join without non-equal condition is supported for delta joins","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/frontend/src/stream_fragmenter/mod.rs","lineNumber":593,"sourceCode":"\n            NodeBody::LocalityProvider(_) => {\n                current_fragment\n                    .fragment_type_mask\n                    .add(FragmentTypeFlag::LocalityProvider);\n            }\n\n            _ => {}\n        };\n\n        // handle join logic\n        if let NodeBody::DeltaIndexJoin(delta_index_join) = stream_node.node_body.as_mut().unwrap()\n        {\n            if delta_index_join.get_join_type()? == JoinType::Inner\n                && delta_index_join.condition.is_none()\n            {\n                return build_delta_join_without_arrange(state, current_fragment, stream_node);\n            } else {\n                panic!(\"only inner join without non-equal condition is supported for delta joins\");\n            }\n        }\n\n        // Usually we do not expect exchange node to be visited here, which should be handled by the\n        // following logic of \"visit children\" instead. If it does happen (for example, `Share` will be\n        // transformed to an `Exchange`), it means we have an empty fragment and we need to add a no-op\n        // node to it, so that the meta service can handle it correctly.\n        if let NodeBody::Exchange(_) = stream_node.node_body.as_ref().unwrap() {\n            stream_node = state.gen_no_op_stream_node(stream_node);\n        }\n\n        // Visit plan children.\n        stream_node.input = stream_node\n            .input\n            .into_iter()\n            .map(|mut child_node| {\n                match child_node.get_node_body()? {\n                    // When exchange node is generated when doing rewrites, it could be having","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/stream_fragmenter/mod.rs#L575-L611","documentation":"This is a panic (not a returned error) in the stream fragmenter's DeltaIndexJoin handling. Delta joins only support inner joins with pure equality conditions (no non-equal predicates); any other delta join shape hits an unreachable else branch and panics. It signals a planner/frontend invariant violation rather than a user-recoverable error.","triggerScenarios":"Building a streaming plan whose fragment contains a DeltaIndexJoin node with a join type other than Inner, or an Inner join that also carries a non-equi condition, when build_fragment() fragments the plan.","commonSituations":"Creating indexes/MVs over joins with mixed ON conditions (e.g. ON a.id = b.id AND a.ts > b.ts) that the delta-join rewrite incorrectly routes through the delta join path; using join types (LEFT/SEMI) on indexed MVs that hit this path.","solutions":["Rewrite the query/MV join to a pure inner equi-join, or move non-equal predicates to WHERE on a single side.","Drop the index that triggers the delta join path so the join uses a regular hash join.","File a bug with the query plan (EXPLAIN) — this panic indicates a missing planner validation.","Use a plain materialized view (without index) for the join and filter on read."],"exampleFix":"-- before\nCREATE INDEX ON mv_delta (a_id);\nSELECT * FROM mv_delta JOIN other ON mv_delta.a_id = other.id AND mv_delta.ts > other.ts;\n\n-- after\nSELECT * FROM mv_delta JOIN other ON mv_delta.a_id = other.id\nWHERE mv_delta.ts > other.ts; -- non-equi predicate outside join condition","handlingStrategy":"validation","validationCode":"-- ensure joins feeding indexed MVs are pure equi inner joins\n-- EXPLAIN the plan and check for DeltaIndexJoin nodes with\n-- non-inner join types or non-equi ON conditions before CREATE INDEX.","typeGuard":null,"tryCatchPattern":"// This is a panic, not an error — guard at plan construction:\nlet join_type = delta_index_join.get_join_type()?;\nlet has_non_equi = delta_index_join.condition\n    .as_ref().map(|c| c.has_non_equi_condition()).unwrap_or(false);\nif join_type != JoinType::Inner || has_non_equi {\n    return build_regular_join(state, current_fragment, stream_node);\n}","preventionTips":["Only create indexes on MVs whose joins are inner equi-joins.","Move non-equal predicates (range, inequality) out of ON clauses.","EXPLAIN plans before CREATE INDEX to spot problematic join shapes.","Report any occurrence upstream — panics here are planner bugs."],"tags":["panic","planner","delta-join","streaming"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}