risingwave_frontend/optimizer/plan_node/
batch_nested_loop_join.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use pretty_xmlish::{Pretty, XmlNode};
16use risingwave_pb::batch_plan::NestedLoopJoinNode;
17use risingwave_pb::batch_plan::plan_node::NodeBody;
18
19use super::batch::prelude::*;
20use super::utils::{Distill, childless_record};
21use super::{
22    BatchPlanRef as PlanRef, ExprRewritable, PlanBase, PlanTreeNodeBinary, ToBatchPb,
23    ToDistributedBatch, generic,
24};
25use crate::error::Result;
26use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor};
27use crate::optimizer::plan_node::ToLocalBatch;
28use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
29use crate::optimizer::plan_node::utils::IndicesDisplay;
30use crate::optimizer::property::{Distribution, Order, RequiredDist};
31use crate::utils::ConditionDisplay;
32
33/// `BatchNestedLoopJoin` implements [`super::LogicalJoin`] by checking the join condition
34/// against all pairs of rows from inner & outer side within 2 layers of loops.
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct BatchNestedLoopJoin {
37    pub base: PlanBase<Batch>,
38    core: generic::Join<PlanRef>,
39}
40
41impl BatchNestedLoopJoin {
42    pub fn new(core: generic::Join<PlanRef>) -> Self {
43        let dist = Self::derive_dist(core.left.distribution(), core.right.distribution());
44        let base = PlanBase::new_batch_with_core(&core, dist, Order::any());
45        Self { base, core }
46    }
47
48    fn derive_dist(left: &Distribution, right: &Distribution) -> Distribution {
49        match (left, right) {
50            (Distribution::Single, Distribution::Single) => Distribution::Single,
51            (_, _) => unreachable!("{}{}", left, right),
52        }
53    }
54}
55
56impl Distill for BatchNestedLoopJoin {
57    fn distill<'a>(&self) -> XmlNode<'a> {
58        let verbose = self.base.ctx().is_explain_verbose();
59        let mut vec = Vec::with_capacity(if verbose { 3 } else { 2 });
60        vec.push(("type", Pretty::debug(&self.core.join_type)));
61
62        let concat_schema = self.core.concat_schema();
63        vec.push((
64            "predicate",
65            Pretty::debug(&ConditionDisplay {
66                condition: &self.core.on,
67                input_schema: &concat_schema,
68            }),
69        ));
70
71        if verbose {
72            let data = IndicesDisplay::from_join(&self.core, &concat_schema);
73            vec.push(("output", data));
74        }
75
76        childless_record("BatchNestedLoopJoin", vec)
77    }
78}
79
80impl PlanTreeNodeBinary<Batch> for BatchNestedLoopJoin {
81    fn left(&self) -> PlanRef {
82        self.core.left.clone()
83    }
84
85    fn right(&self) -> PlanRef {
86        self.core.right.clone()
87    }
88
89    fn clone_with_left_right(&self, left: PlanRef, right: PlanRef) -> Self {
90        let mut core = self.core.clone();
91        core.left = left;
92        core.right = right;
93        Self::new(core)
94    }
95}
96
97impl_plan_tree_node_for_binary! { Batch, BatchNestedLoopJoin }
98
99impl ToDistributedBatch for BatchNestedLoopJoin {
100    fn to_distributed(&self) -> Result<PlanRef> {
101        let left = self
102            .left()
103            .to_distributed_with_required(&Order::any(), &RequiredDist::single())?;
104        let right = self
105            .right()
106            .to_distributed_with_required(&Order::any(), &RequiredDist::single())?;
107
108        Ok(self.clone_with_left_right(left, right).into())
109    }
110}
111
112impl ToBatchPb for BatchNestedLoopJoin {
113    fn to_batch_prost_body(&self) -> NodeBody {
114        NodeBody::NestedLoopJoin(NestedLoopJoinNode {
115            join_type: self.core.join_type as i32,
116            join_cond: Some(ExprImpl::from(self.core.on.clone()).to_expr_proto()),
117            output_indices: self.core.output_indices.iter().map(|&x| x as u32).collect(),
118        })
119    }
120}
121
122impl ToLocalBatch for BatchNestedLoopJoin {
123    fn to_local(&self) -> Result<PlanRef> {
124        let left = RequiredDist::single()
125            .batch_enforce_if_not_satisfies(self.left().to_local()?, &Order::any())?;
126
127        let right = RequiredDist::single()
128            .batch_enforce_if_not_satisfies(self.right().to_local()?, &Order::any())?;
129
130        Ok(self.clone_with_left_right(left, right).into())
131    }
132}
133
134impl ExprRewritable<Batch> for BatchNestedLoopJoin {
135    fn has_rewritable_expr(&self) -> bool {
136        true
137    }
138
139    fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef {
140        let mut core = self.core.clone();
141        core.rewrite_exprs(r);
142        Self::new(core).into()
143    }
144}
145
146impl ExprVisitable for BatchNestedLoopJoin {
147    fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
148        self.core.visit_exprs(v);
149    }
150}