Skip to main content

risingwave_frontend/optimizer/plan_node/
logical_join.rs

1// Copyright 2022 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 std::collections::HashMap;
16use std::ops::Deref;
17
18use fixedbitset::FixedBitSet;
19use itertools::{EitherOrBoth, Itertools};
20use pretty_xmlish::{Pretty, XmlNode};
21use risingwave_expr::bail;
22use risingwave_pb::expr::expr_node::PbType;
23use risingwave_pb::plan_common::{AsOfJoinDesc, JoinType, PbAsOfJoinInequalityType};
24use risingwave_sqlparser::ast::AsOf;
25
26use super::generic::{
27    GenericPlanNode, GenericPlanRef, push_down_into_join, push_down_join_condition,
28};
29use super::utils::{Distill, childless_record};
30use super::{
31    BackfillType, BatchPlanRef, ColPrunable, ExprRewritable, Logical, LogicalPlanRef as PlanRef,
32    PlanBase, PlanTreeNodeBinary, PredicatePushdown, StreamHashJoin, StreamPlanRef, StreamProject,
33    ToBatch, ToStream, generic, try_enforce_locality_requirement,
34};
35use crate::error::{ErrorCode, Result, RwError};
36use crate::expr::{CollectInputRef, Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, InputRef};
37use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
38use crate::optimizer::plan_node::generic::DynamicFilter;
39use crate::optimizer::plan_node::stream_asof_join::StreamAsOfJoin;
40use crate::optimizer::plan_node::utils::IndicesDisplay;
41use crate::optimizer::plan_node::{
42    BatchHashJoin, BatchLookupJoin, BatchNestedLoopJoin, ColumnPruningContext, EqJoinPredicate,
43    LogicalFilter, LogicalScan, PredicatePushdownContext, RewriteStreamContext,
44    StreamDynamicFilter, StreamFilter, StreamTableScan, StreamTemporalJoin, ToStreamContext,
45};
46use crate::optimizer::plan_visitor::LogicalCardinalityExt;
47use crate::optimizer::property::{Distribution, RequiredDist};
48use crate::utils::{ColIndexMapping, ColIndexMappingRewriteExt, Condition, ConditionDisplay};
49
50/// `LogicalJoin` combines two relations according to some condition.
51///
52/// Each output row has fields from the left and right inputs. The set of output rows is a subset
53/// of the cartesian product of the two inputs; precisely which subset depends on the join
54/// condition. In addition, the output columns are a subset of the columns of the left and
55/// right columns, dependent on the output indices provided. A repeat output index is illegal.
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub struct LogicalJoin {
58    pub base: PlanBase<Logical>,
59    core: generic::Join<PlanRef>,
60}
61
62impl Distill for LogicalJoin {
63    fn distill<'a>(&self) -> XmlNode<'a> {
64        let verbose = self.base.ctx().is_explain_verbose();
65        let mut vec = Vec::with_capacity(if verbose { 3 } else { 2 });
66        vec.push(("type", Pretty::debug(&self.join_type())));
67
68        let concat_schema = self.core.concat_schema();
69        let cond = Pretty::debug(&ConditionDisplay {
70            condition: self.on(),
71            input_schema: &concat_schema,
72        });
73        vec.push(("on", cond));
74
75        if verbose {
76            let data = IndicesDisplay::from_join(&self.core, &concat_schema);
77            vec.push(("output", data));
78        }
79
80        childless_record("LogicalJoin", vec)
81    }
82}
83
84impl LogicalJoin {
85    pub(crate) fn new(left: PlanRef, right: PlanRef, join_type: JoinType, on: Condition) -> Self {
86        let core = generic::Join::with_full_output(left, right, join_type, on);
87        Self::with_core(core)
88    }
89
90    pub(crate) fn with_output_indices(
91        left: PlanRef,
92        right: PlanRef,
93        join_type: JoinType,
94        on: Condition,
95        output_indices: Vec<usize>,
96    ) -> Self {
97        let core = generic::Join::new(left, right, on, join_type, output_indices);
98        Self::with_core(core)
99    }
100
101    pub fn with_core(core: generic::Join<PlanRef>) -> Self {
102        let base = PlanBase::new_logical_with_core(&core);
103        LogicalJoin { base, core }
104    }
105
106    pub fn create(
107        left: PlanRef,
108        right: PlanRef,
109        join_type: JoinType,
110        on_clause: ExprImpl,
111    ) -> PlanRef {
112        Self::new(left, right, join_type, Condition::with_expr(on_clause)).into()
113    }
114
115    pub fn internal_column_num(&self) -> usize {
116        self.core.internal_column_num()
117    }
118
119    pub fn i2l_col_mapping_ignore_join_type(&self) -> ColIndexMapping {
120        self.core.i2l_col_mapping_ignore_join_type()
121    }
122
123    pub fn i2r_col_mapping_ignore_join_type(&self) -> ColIndexMapping {
124        self.core.i2r_col_mapping_ignore_join_type()
125    }
126
127    /// Get a reference to the logical join's on.
128    pub fn on(&self) -> &Condition {
129        self.core
130            .on
131            .as_condition_ref()
132            .expect("logical join should store predicate as Condition")
133    }
134
135    pub fn core(&self) -> &generic::Join<PlanRef> {
136        &self.core
137    }
138
139    /// Collect all input ref in the on condition. And separate them into left and right.
140    pub fn input_idx_on_condition(&self) -> (Vec<usize>, Vec<usize>) {
141        let input_refs = self
142            .core
143            .on
144            .as_condition_ref()
145            .expect("logical join should store predicate as Condition")
146            .collect_input_refs(self.core.left.schema().len() + self.core.right.schema().len());
147        let index_group = input_refs
148            .ones()
149            .chunk_by(|i| *i < self.core.left.schema().len());
150        let left_index = index_group
151            .into_iter()
152            .next()
153            .map_or(vec![], |group| group.1.collect_vec());
154        let right_index = index_group.into_iter().next().map_or(vec![], |group| {
155            group
156                .1
157                .map(|i| i - self.core.left.schema().len())
158                .collect_vec()
159        });
160        (left_index, right_index)
161    }
162
163    /// Get the join type of the logical join.
164    pub fn join_type(&self) -> JoinType {
165        self.core.join_type
166    }
167
168    /// Get the eq join key of the logical join.
169    pub fn eq_indexes(&self) -> Vec<(usize, usize)> {
170        self.core.eq_indexes()
171    }
172
173    /// Get the output indices of the logical join.
174    pub fn output_indices(&self) -> &Vec<usize> {
175        &self.core.output_indices
176    }
177
178    /// Clone with new output indices
179    pub fn clone_with_output_indices(&self, output_indices: Vec<usize>) -> Self {
180        Self::with_core(generic::Join {
181            output_indices,
182            ..self.core.clone()
183        })
184    }
185
186    /// Clone with new `on` condition
187    pub fn clone_with_cond(&self, on: Condition) -> Self {
188        Self::with_core(generic::Join {
189            on: generic::JoinOn::Condition(on),
190            ..self.core.clone()
191        })
192    }
193
194    pub fn is_left_join(&self) -> bool {
195        matches!(self.join_type(), JoinType::LeftSemi | JoinType::LeftAnti)
196    }
197
198    pub fn is_right_join(&self) -> bool {
199        matches!(self.join_type(), JoinType::RightSemi | JoinType::RightAnti)
200    }
201
202    pub fn is_full_out(&self) -> bool {
203        self.core.is_full_out()
204    }
205
206    pub fn is_asof_join(&self) -> bool {
207        self.join_type() == JoinType::AsofInner || self.join_type() == JoinType::AsofLeftOuter
208    }
209
210    pub fn output_indices_are_trivial(&self) -> bool {
211        itertools::equal(
212            self.output_indices().iter().cloned(),
213            0..self.internal_column_num(),
214        )
215    }
216
217    /// Try to simplify the outer join with the predicate on the top of the join
218    ///
219    /// now it is just a naive implementation for comparison expression, we can give a more general
220    /// implementation with constant folding in future
221    fn simplify_outer(predicate: &Condition, left_col_num: usize, join_type: JoinType) -> JoinType {
222        let (mut gen_null_in_left, mut gen_null_in_right) = match join_type {
223            JoinType::LeftOuter => (false, true),
224            JoinType::RightOuter => (true, false),
225            JoinType::FullOuter => (true, true),
226            _ => return join_type,
227        };
228
229        for expr in &predicate.conjunctions {
230            if let ExprImpl::FunctionCall(func) = expr {
231                match func.func_type() {
232                    ExprType::Equal
233                    | ExprType::NotEqual
234                    | ExprType::LessThan
235                    | ExprType::LessThanOrEqual
236                    | ExprType::GreaterThan
237                    | ExprType::GreaterThanOrEqual => {
238                        for input in func.inputs() {
239                            if let ExprImpl::InputRef(input) = input {
240                                let idx = input.index;
241                                if idx < left_col_num {
242                                    gen_null_in_left = false;
243                                } else {
244                                    gen_null_in_right = false;
245                                }
246                            }
247                        }
248                    }
249                    _ => {}
250                };
251            }
252        }
253
254        match (gen_null_in_left, gen_null_in_right) {
255            (true, true) => JoinType::FullOuter,
256            (true, false) => JoinType::RightOuter,
257            (false, true) => JoinType::LeftOuter,
258            (false, false) => JoinType::Inner,
259        }
260    }
261
262    /// Index Join:
263    /// Try to convert logical join into batch lookup join and meanwhile it will do
264    /// the index selection for the lookup table so that we can benefit from indexes.
265    fn to_batch_lookup_join_with_index_selection(
266        &self,
267        predicate: EqJoinPredicate,
268        batch_join: generic::Join<BatchPlanRef>,
269    ) -> Result<Option<BatchLookupJoin>> {
270        match batch_join.join_type {
271            JoinType::Inner
272            | JoinType::LeftOuter
273            | JoinType::LeftSemi
274            | JoinType::LeftAnti
275            | JoinType::AsofInner
276            | JoinType::AsofLeftOuter => {}
277            _ => return Ok(None),
278        };
279
280        // Index selection for index join.
281        let right = self.right();
282        // Lookup Join only supports basic tables on the join's right side.
283        let logical_scan: &LogicalScan = if let Some(logical_scan) = right.as_logical_scan() {
284            logical_scan
285        } else {
286            return Ok(None);
287        };
288
289        let mut result_plan = None;
290        // Lookup primary table.
291        if let Some(lookup_join) =
292            self.to_batch_lookup_join(predicate.clone(), batch_join.clone())?
293        {
294            result_plan = Some(lookup_join);
295        }
296
297        if self
298            .core
299            .ctx()
300            .session_ctx()
301            .config()
302            .enable_index_selection()
303        {
304            let indexes = logical_scan.table_indexes();
305            for index in indexes {
306                if let Some(index_scan) = logical_scan.to_index_scan_if_index_covered(index) {
307                    let index_scan: PlanRef = index_scan.into();
308                    let that = self.clone_with_left_right(self.left(), index_scan.clone());
309                    let mut new_batch_join = batch_join.clone();
310                    new_batch_join.right =
311                        index_scan.to_batch().expect("index scan failed to batch");
312
313                    // Lookup covered index.
314                    if let Some(lookup_join) =
315                        that.to_batch_lookup_join(predicate.clone(), new_batch_join)?
316                    {
317                        match &result_plan {
318                            None => result_plan = Some(lookup_join),
319                            Some(prev_lookup_join) => {
320                                // Prefer to choose lookup join with longer lookup prefix len.
321                                if prev_lookup_join.lookup_prefix_len()
322                                    < lookup_join.lookup_prefix_len()
323                                {
324                                    result_plan = Some(lookup_join)
325                                }
326                            }
327                        }
328                    }
329                }
330            }
331        }
332
333        Ok(result_plan)
334    }
335
336    /// Try to convert logical join into batch lookup join.
337    fn to_batch_lookup_join(
338        &self,
339        predicate: EqJoinPredicate,
340        logical_join: generic::Join<BatchPlanRef>,
341    ) -> Result<Option<BatchLookupJoin>> {
342        let logical_scan: &LogicalScan =
343            if let Some(logical_scan) = self.core.right.as_logical_scan() {
344                logical_scan
345            } else {
346                return Ok(None);
347            };
348        Self::gen_batch_lookup_join(logical_scan, predicate, logical_join, self.is_asof_join())
349    }
350
351    pub fn gen_batch_lookup_join(
352        logical_scan: &LogicalScan,
353        predicate: EqJoinPredicate,
354        logical_join: generic::Join<BatchPlanRef>,
355        is_as_of: bool,
356    ) -> Result<Option<BatchLookupJoin>> {
357        match logical_join.join_type {
358            JoinType::Inner
359            | JoinType::LeftOuter
360            | JoinType::LeftSemi
361            | JoinType::LeftAnti
362            | JoinType::AsofInner
363            | JoinType::AsofLeftOuter => {}
364            _ => return Ok(None),
365        };
366
367        let table = logical_scan.table();
368        let output_column_ids = logical_scan.output_column_ids();
369
370        // Verify that the right join key columns are the the prefix of the primary key and
371        // also contain the distribution key.
372        let order_col_ids = table.order_column_ids();
373        let dist_key = table.distribution_key.clone();
374        // The at least prefix of order key that contains distribution key.
375        let mut dist_key_in_order_key_pos = vec![];
376        for d in dist_key {
377            let pos = table
378                .order_column_indices()
379                .position(|x| x == d)
380                .expect("dist_key must in order_key");
381            dist_key_in_order_key_pos.push(pos);
382        }
383        // The shortest prefix of order key that contains distribution key.
384        let shortest_prefix_len = dist_key_in_order_key_pos
385            .iter()
386            .max()
387            .map_or(0, |pos| pos + 1);
388
389        // Distributed lookup join can't support lookup table with a singleton distribution.
390        if shortest_prefix_len == 0 {
391            return Ok(None);
392        }
393
394        // Reorder the join equal predicate to match the order key.
395        let mut reorder_idx = Vec::with_capacity(shortest_prefix_len);
396        for order_col_id in order_col_ids {
397            let mut found = false;
398            for (i, eq_idx) in predicate.right_eq_indexes().into_iter().enumerate() {
399                if order_col_id == output_column_ids[eq_idx] {
400                    reorder_idx.push(i);
401                    found = true;
402                    break;
403                }
404            }
405            if !found {
406                break;
407            }
408        }
409        if reorder_idx.len() < shortest_prefix_len {
410            return Ok(None);
411        }
412        let lookup_prefix_len = reorder_idx.len();
413        let predicate = predicate.reorder(&reorder_idx);
414
415        // Extract the predicate from logical scan. Only pure scan is supported.
416        let (new_scan, scan_predicate, project_expr) = logical_scan.predicate_pull_up();
417        // Construct output column to require column mapping
418        let o2r = if let Some(project_expr) = project_expr {
419            project_expr
420                .into_iter()
421                .map(|x| x.as_input_ref().unwrap().index)
422                .collect_vec()
423        } else {
424            (0..logical_scan.output_col_idx().len()).collect_vec()
425        };
426        let left_schema_len = logical_join.left.schema().len();
427
428        let mut join_predicate_rewriter = LookupJoinPredicateRewriter {
429            offset: left_schema_len,
430            mapping: o2r.clone(),
431        };
432
433        let new_eq_cond = predicate
434            .eq_cond()
435            .rewrite_expr(&mut join_predicate_rewriter);
436
437        let mut scan_predicate_rewriter = LookupJoinScanPredicateRewriter {
438            offset: left_schema_len,
439        };
440
441        let new_other_cond = predicate
442            .other_cond()
443            .clone()
444            .rewrite_expr(&mut join_predicate_rewriter)
445            .and(scan_predicate.rewrite_expr(&mut scan_predicate_rewriter));
446
447        let new_join_on = new_eq_cond.and(new_other_cond);
448        let new_predicate =
449            EqJoinPredicate::create(left_schema_len, new_scan.schema().len(), new_join_on);
450
451        // We discovered that we cannot use a lookup join after pulling up the predicate
452        // from one side and simplifying the condition. Let's use some other join instead.
453        if !new_predicate.has_eq() {
454            return Ok(None);
455        }
456
457        // Rewrite the join output indices and all output indices referred to the old scan need to
458        // rewrite.
459        let new_join_output_indices = logical_join
460            .output_indices
461            .iter()
462            .map(|&x| {
463                if x < left_schema_len {
464                    x
465                } else {
466                    o2r[x - left_schema_len] + left_schema_len
467                }
468            })
469            .collect_vec();
470
471        let new_scan_output_column_ids = new_scan.output_column_ids();
472        let as_of = new_scan.as_of.clone();
473        let new_logical_scan: LogicalScan = new_scan.into();
474
475        // Construct a new logical join, because we have change its RHS.
476        let new_logical_join = generic::Join::new_with_eq_predicate(
477            logical_join.left,
478            new_logical_scan.to_batch()?,
479            new_predicate,
480            logical_join.join_type,
481            new_join_output_indices,
482        );
483
484        let asof_desc = is_as_of
485            .then(|| {
486                Self::get_inequality_desc_from_predicate(
487                    predicate.other_cond().clone(),
488                    left_schema_len,
489                )
490            })
491            .transpose()?;
492
493        Ok(Some(BatchLookupJoin::new(
494            new_logical_join,
495            table.clone(),
496            new_scan_output_column_ids,
497            lookup_prefix_len,
498            false,
499            as_of,
500            asof_desc,
501        )))
502    }
503
504    pub fn decompose(self) -> (PlanRef, PlanRef, Condition, JoinType, Vec<usize>) {
505        self.core.decompose()
506    }
507
508    fn dynamic_filter_candidate(&self, predicate: &Condition) -> Option<(usize, PbType)> {
509        // Dynamic filter only supports `Inner`/`LeftSemi`.
510        if !matches!(self.join_type(), JoinType::Inner | JoinType::LeftSemi) {
511            return None;
512        }
513
514        // Dynamic filter requires right side to be a scalar with one column.
515        if !self.right().max_one_row() || self.right().schema().len() != 1 {
516            return None;
517        }
518
519        // Dynamic filter only supports a single comparison predicate.
520        if predicate.conjunctions.len() > 1 {
521            return None;
522        }
523        let expr: ExprImpl = predicate.clone().into();
524        let (left_ref, comparator, right_ref) = expr.as_comparison_cond()?;
525
526        // Comparison must cross inputs: left input ref vs right scalar input ref.
527        let left_len = self.left().schema().len();
528        let condition_cross_inputs = left_ref.index < left_len && right_ref.index == left_len;
529        if !condition_cross_inputs {
530            return None;
531        }
532
533        // Comparison keys must be type aligned.
534        if self.left().schema().fields()[left_ref.index].data_type
535            != self.right().schema().fields()[0].data_type
536        {
537            return None;
538        }
539
540        // Dynamic filter output can only come from the left side.
541        if !self.output_indices().iter().all(|i| *i < left_len) {
542            return None;
543        }
544
545        Some((left_ref.index, comparator))
546    }
547
548    /// Check whether this join can be treated as a temporal filter for locality optimization.
549    ///
550    /// This is intentionally stricter than dynamic filter:
551    /// - right side must be a `LogicalNow`,
552    /// - and all dynamic filter preconditions must hold.
553    fn temporal_filter_candidate(&self) -> bool {
554        self.right().as_logical_now().is_some()
555            && self.dynamic_filter_candidate(self.on()).is_some()
556    }
557}
558
559impl PlanTreeNodeBinary<Logical> for LogicalJoin {
560    fn left(&self) -> PlanRef {
561        self.core.left.clone()
562    }
563
564    fn right(&self) -> PlanRef {
565        self.core.right.clone()
566    }
567
568    fn clone_with_left_right(&self, left: PlanRef, right: PlanRef) -> Self {
569        Self::with_core(generic::Join {
570            left,
571            right,
572            ..self.core.clone()
573        })
574    }
575
576    fn rewrite_with_left_right(
577        &self,
578        left: PlanRef,
579        left_col_change: ColIndexMapping,
580        right: PlanRef,
581        right_col_change: ColIndexMapping,
582    ) -> (Self, ColIndexMapping) {
583        let (new_on, new_output_indices) = {
584            let (mut map, _) = left_col_change.clone().into_parts();
585            let (mut right_map, _) = right_col_change.clone().into_parts();
586            for i in right_map.iter_mut().flatten() {
587                *i += left.schema().len();
588            }
589            map.append(&mut right_map);
590            let mut mapping = ColIndexMapping::new(map, left.schema().len() + right.schema().len());
591
592            let new_output_indices = self
593                .output_indices()
594                .iter()
595                .map(|&i| mapping.map(i))
596                .collect::<Vec<_>>();
597            let new_on = self.on().clone().rewrite_expr(&mut mapping);
598            (new_on, new_output_indices)
599        };
600
601        let join = Self::with_output_indices(
602            left,
603            right,
604            self.join_type(),
605            new_on,
606            new_output_indices.clone(),
607        );
608
609        let new_i2o = ColIndexMapping::with_remaining_columns(
610            &new_output_indices,
611            join.internal_column_num(),
612        );
613
614        let old_o2i = self.core.o2i_col_mapping();
615
616        let old_o2l = old_o2i
617            .composite(&self.core.i2l_col_mapping())
618            .composite(&left_col_change);
619        let old_o2r = old_o2i
620            .composite(&self.core.i2r_col_mapping())
621            .composite(&right_col_change);
622        let new_l2o = join.core.l2i_col_mapping().composite(&new_i2o);
623        let new_r2o = join.core.r2i_col_mapping().composite(&new_i2o);
624
625        let out_col_change = old_o2l
626            .composite(&new_l2o)
627            .union(&old_o2r.composite(&new_r2o));
628        (join, out_col_change)
629    }
630}
631
632impl_plan_tree_node_for_binary! { Logical, LogicalJoin }
633
634impl ColPrunable for LogicalJoin {
635    fn prune_col(&self, required_cols: &[usize], ctx: &mut ColumnPruningContext) -> PlanRef {
636        // make `required_cols` point to internal table instead of output schema.
637        let required_cols = required_cols
638            .iter()
639            .map(|i| self.output_indices()[*i])
640            .collect_vec();
641        let left_len = self.left().schema().fields.len();
642
643        let total_len = self.left().schema().len() + self.right().schema().len();
644        let mut resized_required_cols = FixedBitSet::with_capacity(total_len);
645
646        required_cols.iter().for_each(|&i| {
647            if self.is_right_join() {
648                resized_required_cols.insert(left_len + i);
649            } else {
650                resized_required_cols.insert(i);
651            }
652        });
653
654        // add those columns which are required in the join condition to
655        // to those that are required in the output
656        let mut visitor = CollectInputRef::new(resized_required_cols);
657        self.on().visit_expr(&mut visitor);
658        let left_right_required_cols = FixedBitSet::from(visitor).ones().collect_vec();
659
660        let mut left_required_cols = Vec::new();
661        let mut right_required_cols = Vec::new();
662        left_right_required_cols.iter().for_each(|&i| {
663            if i < left_len {
664                left_required_cols.push(i);
665            } else {
666                right_required_cols.push(i - left_len);
667            }
668        });
669
670        let mut on = self.on().clone();
671        let mut mapping =
672            ColIndexMapping::with_remaining_columns(&left_right_required_cols, total_len);
673        on = on.rewrite_expr(&mut mapping);
674
675        let new_output_indices = {
676            let required_inputs_in_output = if self.is_left_join() {
677                &left_required_cols
678            } else if self.is_right_join() {
679                &right_required_cols
680            } else {
681                &left_right_required_cols
682            };
683
684            let mapping =
685                ColIndexMapping::with_remaining_columns(required_inputs_in_output, total_len);
686            required_cols.iter().map(|&i| mapping.map(i)).collect_vec()
687        };
688
689        LogicalJoin::with_output_indices(
690            self.left().prune_col(&left_required_cols, ctx),
691            self.right().prune_col(&right_required_cols, ctx),
692            self.join_type(),
693            on,
694            new_output_indices,
695        )
696        .into()
697    }
698}
699
700impl ExprRewritable<Logical> for LogicalJoin {
701    fn has_rewritable_expr(&self) -> bool {
702        true
703    }
704
705    fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef {
706        let mut core = self.core.clone();
707        core.rewrite_exprs(r);
708        Self {
709            base: self.base.clone_with_new_plan_id(),
710            core,
711        }
712        .into()
713    }
714}
715
716impl ExprVisitable for LogicalJoin {
717    fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
718        self.core.visit_exprs(v);
719    }
720}
721
722/// We are trying to derive a predicate to apply to the other side of a join if all
723/// the `InputRef`s in the predicate are eq condition columns, and can hence be substituted
724/// with the corresponding eq condition columns of the other side.
725///
726/// Strategy:
727/// 1. If the function is pure except for any `InputRef` (which may refer to impure computation),
728///    then we proceed. Else abort.
729/// 2. Then, we collect `InputRef`s in the conjunction.
730/// 3. If they are all columns in the given side of join eq condition, then we proceed. Else abort.
731/// 4. We then rewrite the `ExprImpl`, by replacing `InputRef` column indices with the equivalent in
732///    the other side.
733///
734/// # Arguments
735///
736/// Suppose we derive a predicate from the left side to be pushed to the right side.
737/// * `expr`: An expr from the left side.
738/// * `col_num`: The number of columns in the left side.
739fn derive_predicate_from_eq_condition(
740    expr: &ExprImpl,
741    eq_condition: &EqJoinPredicate,
742    col_num: usize,
743    expr_is_left: bool,
744) -> Option<ExprImpl> {
745    if expr.is_impure() {
746        return None;
747    }
748    let eq_indices = eq_condition
749        .eq_indexes_typed()
750        .iter()
751        .filter_map(|(l, r)| {
752            if l.return_type() != r.return_type() {
753                None
754            } else if expr_is_left {
755                Some(l.index())
756            } else {
757                Some(r.index())
758            }
759        })
760        .collect_vec();
761    if expr
762        .collect_input_refs(col_num)
763        .ones()
764        .any(|index| !eq_indices.contains(&index))
765    {
766        // expr contains an InputRef not in eq_condition
767        return None;
768    }
769    // The function is pure except for `InputRef` and all `InputRef`s are `eq_condition` indices.
770    // Hence, we can substitute those `InputRef`s with indices from the other side.
771    let other_side_mapping = if expr_is_left {
772        eq_condition.eq_indexes_typed().into_iter().collect()
773    } else {
774        eq_condition
775            .eq_indexes_typed()
776            .into_iter()
777            .map(|(x, y)| (y, x))
778            .collect()
779    };
780    struct InputRefsRewriter {
781        mapping: HashMap<InputRef, InputRef>,
782    }
783    impl ExprRewriter for InputRefsRewriter {
784        fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
785            self.mapping[&input_ref].clone().into()
786        }
787    }
788    Some(
789        InputRefsRewriter {
790            mapping: other_side_mapping,
791        }
792        .rewrite_expr(expr.clone()),
793    )
794}
795
796/// Rewrite the join predicate and all columns referred to the scan side need to rewrite.
797struct LookupJoinPredicateRewriter {
798    offset: usize,
799    mapping: Vec<usize>,
800}
801impl ExprRewriter for LookupJoinPredicateRewriter {
802    fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
803        if input_ref.index() < self.offset {
804            input_ref.into()
805        } else {
806            InputRef::new(
807                self.mapping[input_ref.index() - self.offset] + self.offset,
808                input_ref.return_type(),
809            )
810            .into()
811        }
812    }
813}
814
815/// Rewrite the scan predicate so we can add it to the join predicate.
816struct LookupJoinScanPredicateRewriter {
817    offset: usize,
818}
819impl ExprRewriter for LookupJoinScanPredicateRewriter {
820    fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
821        InputRef::new(input_ref.index() + self.offset, input_ref.return_type()).into()
822    }
823}
824
825impl PredicatePushdown for LogicalJoin {
826    /// Pushes predicates above and within a join node into the join node and/or its children nodes.
827    ///
828    /// # Which predicates can be pushed
829    ///
830    /// For inner join, we can do all kinds of pushdown.
831    ///
832    /// For left/right semi join, we can push filter to left/right and on-clause,
833    /// and push on-clause to left/right.
834    ///
835    /// For left/right anti join, we can push filter to left/right, but on-clause can not be pushed
836    ///
837    /// ## Outer Join
838    ///
839    /// Preserved Row table
840    /// : The table in an Outer Join that must return all rows.
841    ///
842    /// Null Supplying table
843    /// : This is the table that has nulls filled in for its columns in unmatched rows.
844    ///
845    /// |                          | Preserved Row table | Null Supplying table |
846    /// |--------------------------|---------------------|----------------------|
847    /// | Join predicate (on)      | Not Pushed          | Pushed               |
848    /// | Where predicate (filter) | Pushed              | Not Pushed           |
849    fn predicate_pushdown(
850        &self,
851        predicate: Condition,
852        ctx: &mut PredicatePushdownContext,
853    ) -> PlanRef {
854        // rewrite output col referencing indices as internal cols
855        let mut predicate = {
856            let mut mapping = self.core.o2i_col_mapping();
857            predicate.rewrite_expr(&mut mapping)
858        };
859
860        let left_col_num = self.left().schema().len();
861        let right_col_num = self.right().schema().len();
862        let join_type = LogicalJoin::simplify_outer(&predicate, left_col_num, self.join_type());
863
864        let push_down_temporal_predicate = self.temporal_join_on().is_none();
865
866        let (left_from_filter, right_from_filter, on) = push_down_into_join(
867            &mut predicate,
868            left_col_num,
869            right_col_num,
870            join_type,
871            push_down_temporal_predicate,
872        );
873
874        let mut new_on = self.on().clone().and(on);
875        let (left_from_on, right_from_on) = push_down_join_condition(
876            &mut new_on,
877            left_col_num,
878            right_col_num,
879            join_type,
880            push_down_temporal_predicate,
881        );
882
883        let left_predicate = left_from_filter.and(left_from_on);
884        let right_predicate = right_from_filter.and(right_from_on);
885
886        // Derive conditions to push to the other side based on eq condition columns
887        let eq_condition = EqJoinPredicate::create(left_col_num, right_col_num, new_on.clone());
888
889        // Only push to RHS if RHS is inner side of a join (RHS requires match on LHS)
890        let right_from_left = if matches!(
891            join_type,
892            JoinType::Inner | JoinType::LeftOuter | JoinType::RightSemi | JoinType::LeftSemi
893        ) {
894            Condition {
895                conjunctions: left_predicate
896                    .conjunctions
897                    .iter()
898                    .filter_map(|expr| {
899                        derive_predicate_from_eq_condition(expr, &eq_condition, left_col_num, true)
900                    })
901                    .collect(),
902            }
903        } else {
904            Condition::true_cond()
905        };
906
907        // Only push to LHS if LHS is inner side of a join (LHS requires match on RHS)
908        let left_from_right = if matches!(
909            join_type,
910            JoinType::Inner | JoinType::RightOuter | JoinType::LeftSemi | JoinType::RightSemi
911        ) {
912            Condition {
913                conjunctions: right_predicate
914                    .conjunctions
915                    .iter()
916                    .filter_map(|expr| {
917                        derive_predicate_from_eq_condition(
918                            expr,
919                            &eq_condition,
920                            right_col_num,
921                            false,
922                        )
923                    })
924                    .collect(),
925            }
926        } else {
927            Condition::true_cond()
928        };
929
930        let left_predicate = left_predicate.and(left_from_right);
931        let right_predicate = right_predicate.and(right_from_left);
932
933        let new_left = self.left().predicate_pushdown(left_predicate, ctx);
934        let new_right = self.right().predicate_pushdown(right_predicate, ctx);
935        let new_join = LogicalJoin::with_output_indices(
936            new_left,
937            new_right,
938            join_type,
939            new_on,
940            self.output_indices().clone(),
941        );
942
943        let mut mapping = self.core.i2o_col_mapping();
944        predicate = predicate.rewrite_expr(&mut mapping);
945        LogicalFilter::create(new_join.into(), predicate)
946    }
947}
948
949#[derive(Clone, Copy)]
950struct TemporalJoinScan<'a>(&'a LogicalScan);
951
952impl<'a> Deref for TemporalJoinScan<'a> {
953    type Target = LogicalScan;
954
955    fn deref(&self) -> &Self::Target {
956        self.0
957    }
958}
959
960impl LogicalJoin {
961    fn get_stream_input_for_hash_join(
962        &self,
963        predicate: &EqJoinPredicate,
964        ctx: &mut ToStreamContext,
965    ) -> Result<(StreamPlanRef, StreamPlanRef)> {
966        use super::stream::prelude::*;
967
968        let mut right = self.right().to_stream_with_dist_required(
969            &RequiredDist::shard_by_key(self.right().schema().len(), &predicate.right_eq_indexes()),
970            ctx,
971        )?;
972        let r2l =
973            predicate.r2l_eq_columns_mapping(self.left().schema().len(), right.schema().len());
974        let l2r =
975            predicate.l2r_eq_columns_mapping(self.left().schema().len(), right.schema().len());
976        let mut left;
977        let right_dist = right.distribution();
978        match right_dist {
979            Distribution::HashShard(_) => {
980                let left_dist = r2l
981                    .rewrite_required_distribution(&RequiredDist::PhysicalDist(right_dist.clone()));
982                left = self.left().to_stream_with_dist_required(&left_dist, ctx)?;
983            }
984            Distribution::UpstreamHashShard(_, _) => {
985                left = self.left().to_stream_with_dist_required(
986                    &RequiredDist::shard_by_key(
987                        self.left().schema().len(),
988                        &predicate.left_eq_indexes(),
989                    ),
990                    ctx,
991                )?;
992                let left_dist = left.distribution();
993                match left_dist {
994                    Distribution::HashShard(_) => {
995                        let right_dist = l2r.rewrite_required_distribution(
996                            &RequiredDist::PhysicalDist(left_dist.clone()),
997                        );
998                        right = right_dist.streaming_enforce_if_not_satisfies(right)?
999                    }
1000                    Distribution::UpstreamHashShard(_, _) => {
1001                        left = RequiredDist::hash_shard(&predicate.left_eq_indexes())
1002                            .streaming_enforce_if_not_satisfies(left)?;
1003                        right = RequiredDist::hash_shard(&predicate.right_eq_indexes())
1004                            .streaming_enforce_if_not_satisfies(right)?;
1005                    }
1006                    _ => unreachable!(),
1007                }
1008            }
1009            _ => unreachable!(),
1010        }
1011        Ok((left, right))
1012    }
1013
1014    fn to_stream_hash_join(
1015        &self,
1016        predicate: EqJoinPredicate,
1017        ctx: &mut ToStreamContext,
1018    ) -> Result<StreamPlanRef> {
1019        use super::stream::prelude::*;
1020
1021        assert!(predicate.has_eq());
1022        let (left, right) = self.get_stream_input_for_hash_join(&predicate, ctx)?;
1023
1024        let mut core = self.core.clone_with_inputs(left, right);
1025        core.on = generic::JoinOn::EqPredicate(predicate);
1026
1027        // Convert to Hash Join for equal joins
1028        // For inner joins, pull non-equal conditions to a filter operator on top of it by default.
1029        // We do so as the filter operator can apply the non-equal condition batch-wise (vectorized)
1030        // as opposed to the HashJoin, which applies the condition row-wise.
1031        // However, the default behavior of pulling up non-equal conditions can be overridden by the
1032        // session variable `streaming_force_filter_inside_join` as it can save unnecessary
1033        // materialization of rows only to be filtered later.
1034
1035        let stream_hash_join = StreamHashJoin::new(core.clone())?;
1036        let predicate = stream_hash_join.eq_join_predicate().clone();
1037
1038        let force_filter_inside_join = self
1039            .base
1040            .ctx()
1041            .session_ctx()
1042            .config()
1043            .streaming_force_filter_inside_join();
1044
1045        let pull_filter = self.join_type() == JoinType::Inner
1046            && stream_hash_join.eq_join_predicate().has_non_eq()
1047            && stream_hash_join.inequality_pairs().is_empty()
1048            && (!force_filter_inside_join);
1049        if pull_filter {
1050            let default_indices = (0..self.internal_column_num()).collect::<Vec<_>>();
1051
1052            let mut core = core;
1053            core.output_indices = default_indices.clone();
1054            // Temporarily remove output indices.
1055            let eq_cond = EqJoinPredicate::new(
1056                Condition::true_cond(),
1057                predicate.eq_keys().to_vec(),
1058                self.left().schema().len(),
1059                self.right().schema().len(),
1060            );
1061            core.on = generic::JoinOn::EqPredicate(eq_cond);
1062            let hash_join = StreamHashJoin::new(core)?.into();
1063            let logical_filter = generic::Filter::new(predicate.non_eq_cond(), hash_join);
1064            let plan = StreamFilter::new(logical_filter).into();
1065            if self.output_indices() != &default_indices {
1066                let logical_project = generic::Project::with_mapping(
1067                    plan,
1068                    ColIndexMapping::with_remaining_columns(
1069                        self.output_indices(),
1070                        self.internal_column_num(),
1071                    ),
1072                );
1073                Ok(StreamProject::new(logical_project).into())
1074            } else {
1075                Ok(plan)
1076            }
1077        } else {
1078            Ok(stream_hash_join.into())
1079        }
1080    }
1081
1082    pub fn should_be_temporal_join(&self) -> bool {
1083        self.temporal_join_on().is_some()
1084    }
1085
1086    fn temporal_join_on(&self) -> Option<TemporalJoinScan<'_>> {
1087        if let Some(logical_scan) = self.core.right.as_logical_scan() {
1088            matches!(logical_scan.as_of(), Some(AsOf::ProcessTime))
1089                .then_some(TemporalJoinScan(logical_scan))
1090        } else {
1091            None
1092        }
1093    }
1094
1095    fn should_be_stream_temporal_join<'a>(
1096        &'a self,
1097        ctx: &ToStreamContext,
1098    ) -> Result<Option<TemporalJoinScan<'a>>> {
1099        Ok(if let Some(scan) = self.temporal_join_on() {
1100            if ctx.backfill_type().is_snapshot_backfill() {
1101                return Err(RwError::from(ErrorCode::NotSupported(
1102                    "Temporal join with snapshot backfill not supported".into(),
1103                    "Please use arrangement backfill".into(),
1104                )));
1105            }
1106            if scan.cross_database() {
1107                return Err(RwError::from(ErrorCode::NotSupported(
1108                        "Temporal join requires the lookup table to be in the same database as the stream source table".into(),
1109                        "Please ensure both tables are in the same database".into(),
1110                    )));
1111            }
1112            Some(scan)
1113        } else {
1114            None
1115        })
1116    }
1117
1118    fn to_stream_temporal_join_with_index_selection(
1119        &self,
1120        logical_scan: TemporalJoinScan<'_>,
1121        predicate: EqJoinPredicate,
1122        ctx: &mut ToStreamContext,
1123    ) -> Result<StreamPlanRef> {
1124        // Use primary table.
1125        let mut result_plan: Result<StreamTemporalJoin> =
1126            self.to_stream_temporal_join(logical_scan, predicate.clone(), ctx);
1127        // Return directly if this temporal join can match the pk of its right table.
1128        if let Ok(temporal_join) = &result_plan
1129            && temporal_join.eq_join_predicate().eq_indexes().len()
1130                == logical_scan.primary_key().len()
1131        {
1132            return result_plan.map(|x| x.into());
1133        }
1134        if self
1135            .core
1136            .ctx()
1137            .session_ctx()
1138            .config()
1139            .enable_index_selection()
1140        {
1141            let indexes = logical_scan.table_indexes();
1142            for index in indexes {
1143                // Use index table
1144                if let Some(index_scan) = logical_scan.to_index_scan_if_index_covered(index) {
1145                    let index_scan: PlanRef = index_scan.into();
1146                    let that = self.clone_with_left_right(self.left(), index_scan.clone());
1147                    if let Ok(temporal_join) = that.to_stream_temporal_join(
1148                        that.temporal_join_on().expect(
1149                            "index scan created from temporal join scan must also be temporal join",
1150                        ),
1151                        predicate.clone(),
1152                        ctx,
1153                    ) {
1154                        match &result_plan {
1155                            Err(_) => result_plan = Ok(temporal_join),
1156                            Ok(prev_temporal_join) => {
1157                                // Prefer to the temporal join with a longer lookup prefix len.
1158                                if prev_temporal_join.eq_join_predicate().eq_indexes().len()
1159                                    < temporal_join.eq_join_predicate().eq_indexes().len()
1160                                {
1161                                    result_plan = Ok(temporal_join)
1162                                }
1163                            }
1164                        }
1165                    }
1166                }
1167            }
1168        }
1169
1170        result_plan.map(|x| x.into())
1171    }
1172
1173    fn temporal_join_scan_predicate_pull_up(
1174        logical_scan: TemporalJoinScan<'_>,
1175        predicate: EqJoinPredicate,
1176        output_indices: &[usize],
1177        left_schema_len: usize,
1178    ) -> Result<(StreamTableScan, EqJoinPredicate, Condition, Vec<usize>)> {
1179        // Extract the predicate from logical scan. Only pure scan is supported.
1180        let (new_scan, scan_predicate, project_expr) = logical_scan.predicate_pull_up();
1181        // Construct output column to require column mapping
1182        let o2r = if let Some(project_expr) = project_expr {
1183            project_expr
1184                .into_iter()
1185                .map(|x| x.as_input_ref().unwrap().index)
1186                .collect_vec()
1187        } else {
1188            (0..logical_scan.output_col_idx().len()).collect_vec()
1189        };
1190        let mut join_predicate_rewriter = LookupJoinPredicateRewriter {
1191            offset: left_schema_len,
1192            mapping: o2r.clone(),
1193        };
1194
1195        let new_eq_cond = predicate
1196            .eq_cond()
1197            .rewrite_expr(&mut join_predicate_rewriter);
1198
1199        let mut scan_predicate_rewriter = LookupJoinScanPredicateRewriter {
1200            offset: left_schema_len,
1201        };
1202
1203        let new_other_cond = predicate
1204            .other_cond()
1205            .clone()
1206            .rewrite_expr(&mut join_predicate_rewriter)
1207            .and(scan_predicate.rewrite_expr(&mut scan_predicate_rewriter));
1208
1209        let new_join_on = new_eq_cond.and(new_other_cond);
1210
1211        let new_predicate = EqJoinPredicate::create(
1212            left_schema_len,
1213            new_scan.schema().len(),
1214            new_join_on.clone(),
1215        );
1216
1217        // Rewrite the join output indices and all output indices referred to the old scan need to
1218        // rewrite.
1219        let new_join_output_indices = output_indices
1220            .iter()
1221            .map(|&x| {
1222                if x < left_schema_len {
1223                    x
1224                } else {
1225                    o2r[x - left_schema_len] + left_schema_len
1226                }
1227            })
1228            .collect_vec();
1229
1230        let new_stream_table_scan =
1231            StreamTableScan::new_with_backfill_type(new_scan, BackfillType::Replicated);
1232        Ok((
1233            new_stream_table_scan,
1234            new_predicate,
1235            new_join_on,
1236            new_join_output_indices,
1237        ))
1238    }
1239
1240    fn to_stream_temporal_join(
1241        &self,
1242        logical_scan: TemporalJoinScan<'_>,
1243        predicate: EqJoinPredicate,
1244        ctx: &mut ToStreamContext,
1245    ) -> Result<StreamTemporalJoin> {
1246        use super::stream::prelude::*;
1247
1248        assert!(predicate.has_eq());
1249
1250        let table = logical_scan.table();
1251        let output_column_ids = logical_scan.output_column_ids();
1252
1253        // Verify that the right join key columns are the the prefix of the primary key and
1254        // also contain the distribution key.
1255        let order_col_ids = table.order_column_ids();
1256        let dist_key = table.distribution_key.clone();
1257
1258        let mut dist_key_in_order_key_pos = vec![];
1259        for d in dist_key {
1260            let pos = table
1261                .order_column_indices()
1262                .position(|x| x == d)
1263                .expect("dist_key must in order_key");
1264            dist_key_in_order_key_pos.push(pos);
1265        }
1266        // The shortest prefix of order key that contains distribution key.
1267        let shortest_prefix_len = dist_key_in_order_key_pos
1268            .iter()
1269            .max()
1270            .map_or(0, |pos| pos + 1);
1271
1272        // Reorder the join equal predicate to match the order key.
1273        let mut reorder_idx = Vec::with_capacity(shortest_prefix_len);
1274        for order_col_id in order_col_ids {
1275            let mut found = false;
1276            for (i, eq_idx) in predicate.right_eq_indexes().into_iter().enumerate() {
1277                if order_col_id == output_column_ids[eq_idx] {
1278                    reorder_idx.push(i);
1279                    found = true;
1280                    break;
1281                }
1282            }
1283            if !found {
1284                break;
1285            }
1286        }
1287        if reorder_idx.len() < shortest_prefix_len {
1288            return Err(RwError::from(ErrorCode::NotSupported(
1289                "Temporal join requires the equivalence join condition includes the key columns that form the distribution key of the lookup table".into(),
1290                concat!(
1291                    "Use DESCRIBE <table_name> to view the table's key information.\n",
1292                    "You can create an index on the lookup table to facilitate the temporal join if necessary."
1293                ).into(),
1294            )));
1295        }
1296        let lookup_prefix_len = reorder_idx.len();
1297        let predicate = predicate.reorder(&reorder_idx);
1298
1299        let required_dist = if dist_key_in_order_key_pos.is_empty() {
1300            RequiredDist::single()
1301        } else {
1302            let left_eq_indexes = predicate.left_eq_indexes();
1303            let left_dist_key = dist_key_in_order_key_pos
1304                .iter()
1305                .map(|pos| left_eq_indexes[*pos])
1306                .collect_vec();
1307
1308            RequiredDist::hash_shard(&left_dist_key)
1309        };
1310
1311        let left = self.left().to_stream(ctx)?;
1312        // Enforce a shuffle for the temporal join LHS to let the scheduler be able to schedule the join fragment together with the RHS with a `no_shuffle` exchange.
1313        let left = required_dist.stream_enforce(left);
1314
1315        let (new_stream_table_scan, new_predicate, new_join_on, new_join_output_indices) =
1316            Self::temporal_join_scan_predicate_pull_up(
1317                logical_scan,
1318                predicate,
1319                self.output_indices(),
1320                self.left().schema().len(),
1321            )?;
1322
1323        let right = RequiredDist::no_shuffle(new_stream_table_scan.into());
1324        if !new_predicate.has_eq() {
1325            return Err(RwError::from(ErrorCode::NotSupported(
1326                "Temporal join requires a non trivial join condition".into(),
1327                "Please remove the false condition of the join".into(),
1328            )));
1329        }
1330
1331        // Construct a new logical join, because we have change its RHS.
1332        let new_logical_join = generic::Join::new(
1333            left,
1334            right,
1335            new_join_on,
1336            self.join_type(),
1337            new_join_output_indices,
1338        );
1339
1340        let new_predicate = new_predicate.retain_prefix_eq_key(lookup_prefix_len);
1341
1342        let mut new_logical_join = new_logical_join;
1343        new_logical_join.on = generic::JoinOn::EqPredicate(new_predicate);
1344        StreamTemporalJoin::new(new_logical_join, false)
1345    }
1346
1347    fn to_stream_nested_loop_temporal_join(
1348        &self,
1349        logical_scan: TemporalJoinScan<'_>,
1350        predicate: EqJoinPredicate,
1351        ctx: &mut ToStreamContext,
1352    ) -> Result<StreamPlanRef> {
1353        use super::stream::prelude::*;
1354        assert!(!predicate.has_eq());
1355
1356        let left = self.left().to_stream_with_dist_required(
1357            &RequiredDist::PhysicalDist(Distribution::Broadcast),
1358            ctx,
1359        )?;
1360        assert!(left.as_stream_exchange().is_some());
1361
1362        if self.join_type() != JoinType::Inner {
1363            return Err(RwError::from(ErrorCode::NotSupported(
1364                "Temporal join requires an inner join".into(),
1365                "Please use an inner join".into(),
1366            )));
1367        }
1368
1369        if !left.append_only() {
1370            return Err(RwError::from(ErrorCode::NotSupported(
1371                "Nested-loop Temporal join requires the left hash side to be append only".into(),
1372                "Please ensure the left hash side is append only".into(),
1373            )));
1374        }
1375
1376        let (new_stream_table_scan, new_predicate, new_join_on, new_join_output_indices) =
1377            Self::temporal_join_scan_predicate_pull_up(
1378                logical_scan,
1379                predicate,
1380                self.output_indices(),
1381                self.left().schema().len(),
1382            )?;
1383
1384        let right = RequiredDist::no_shuffle(new_stream_table_scan.into());
1385
1386        // Construct a new logical join, because we have change its RHS.
1387        let new_logical_join = generic::Join::new(
1388            left,
1389            right,
1390            new_join_on,
1391            self.join_type(),
1392            new_join_output_indices,
1393        );
1394
1395        let mut new_logical_join = new_logical_join;
1396        new_logical_join.on = generic::JoinOn::EqPredicate(new_predicate);
1397        Ok(StreamTemporalJoin::new(new_logical_join, true)?.into())
1398    }
1399
1400    fn to_stream_dynamic_filter(
1401        &self,
1402        predicate: Condition,
1403        ctx: &mut ToStreamContext,
1404    ) -> Result<Option<StreamPlanRef>> {
1405        use super::stream::prelude::*;
1406
1407        // If there is exactly one predicate, it is a comparison (<, <=, >, >=), and the
1408        // join is a `Inner` or `LeftSemi` join, we can convert the scalar subquery into a
1409        // `StreamDynamicFilter`
1410        let Some((left_key_idx, comparator)) = self.dynamic_filter_candidate(&predicate) else {
1411            return Ok(None);
1412        };
1413
1414        let left = self.left().to_stream(ctx)?.enforce_concrete_distribution();
1415        let right = self.right().to_stream_with_dist_required(
1416            &RequiredDist::PhysicalDist(Distribution::Broadcast),
1417            ctx,
1418        )?;
1419
1420        assert!(right.as_stream_exchange().is_some());
1421        assert_eq!(
1422            *Itertools::exactly_one(right.inputs().iter())
1423                .unwrap()
1424                .distribution(),
1425            Distribution::Single
1426        );
1427
1428        let core = DynamicFilter::new(comparator, left_key_idx, left, right);
1429        let plan = StreamDynamicFilter::new(core)?.into();
1430        // TODO: `DynamicFilterExecutor` should support `output_indices` in `ChunkBuilder`
1431        if self
1432            .output_indices()
1433            .iter()
1434            .copied()
1435            .ne(0..self.left().schema().len())
1436        {
1437            // The schema of dynamic filter is always the same as the left side now, and we have
1438            // checked that all output columns are from the left side before.
1439            let logical_project = generic::Project::with_mapping(
1440                plan,
1441                ColIndexMapping::with_remaining_columns(
1442                    self.output_indices(),
1443                    self.left().schema().len(),
1444                ),
1445            );
1446            Ok(Some(StreamProject::new(logical_project).into()))
1447        } else {
1448            Ok(Some(plan))
1449        }
1450    }
1451
1452    pub fn index_lookup_join_to_batch_lookup_join(&self) -> Result<Option<BatchPlanRef>> {
1453        let predicate = EqJoinPredicate::create(
1454            self.left().schema().len(),
1455            self.right().schema().len(),
1456            self.on().clone(),
1457        );
1458        assert!(predicate.has_eq());
1459
1460        let join = self
1461            .core
1462            .clone_with_inputs(self.core.left.to_batch()?, self.core.right.to_batch()?);
1463
1464        Ok(self.to_batch_lookup_join(predicate, join)?.map(Into::into))
1465    }
1466
1467    fn to_stream_asof_join(
1468        &self,
1469        predicate: EqJoinPredicate,
1470        ctx: &mut ToStreamContext,
1471    ) -> Result<StreamPlanRef> {
1472        use super::stream::prelude::*;
1473
1474        if predicate.eq_keys().is_empty() {
1475            return Err(ErrorCode::InvalidInputSyntax(
1476                "AsOf join requires at least 1 equal condition".to_owned(),
1477            )
1478            .into());
1479        }
1480
1481        let (left, right) = self.get_stream_input_for_hash_join(&predicate, ctx)?;
1482        let left_len = left.schema().len();
1483        let mut core = self.core.clone_with_inputs(left, right);
1484        core.on = generic::JoinOn::EqPredicate(predicate);
1485
1486        let inequality_desc = Self::get_inequality_desc_from_predicate(
1487            core.on
1488                .as_eq_predicate_ref()
1489                .expect("core predicate must exist")
1490                .other_cond()
1491                .clone(),
1492            left_len,
1493        )?;
1494
1495        Ok(StreamAsOfJoin::new(core, inequality_desc)?.into())
1496    }
1497
1498    /// Convert the logical join to a Hash join.
1499    fn to_batch_hash_join(
1500        &self,
1501        logical_join: generic::Join<BatchPlanRef>,
1502        predicate: EqJoinPredicate,
1503    ) -> Result<BatchPlanRef> {
1504        use super::batch::prelude::*;
1505
1506        let left_schema_len = logical_join.left.schema().len();
1507        let asof_desc = self
1508            .is_asof_join()
1509            .then(|| {
1510                Self::get_inequality_desc_from_predicate(
1511                    predicate.other_cond().clone(),
1512                    left_schema_len,
1513                )
1514            })
1515            .transpose()?;
1516
1517        let logical_join = generic::Join {
1518            on: generic::JoinOn::EqPredicate(predicate),
1519            ..logical_join
1520        };
1521        let batch_join = BatchHashJoin::new(logical_join, asof_desc);
1522        Ok(batch_join.into())
1523    }
1524
1525    pub fn get_inequality_desc_from_predicate(
1526        predicate: Condition,
1527        left_input_len: usize,
1528    ) -> Result<AsOfJoinDesc> {
1529        let expr: ExprImpl = predicate.into();
1530        if let Some((left_input_ref, expr_type, right_input_ref)) = expr.as_comparison_cond() {
1531            if left_input_ref.index() < left_input_len && right_input_ref.index() >= left_input_len
1532            {
1533                Ok(AsOfJoinDesc {
1534                    left_idx: left_input_ref.index() as u32,
1535                    right_idx: (right_input_ref.index() - left_input_len) as u32,
1536                    inequality_type: Self::expr_type_to_comparison_type(expr_type)?.into(),
1537                })
1538            } else {
1539                bail!("inequal condition from the same side should be push down in optimizer");
1540            }
1541        } else {
1542            Err(ErrorCode::InvalidInputSyntax(
1543                "AsOf join requires exactly 1 ineuquality condition".to_owned(),
1544            )
1545            .into())
1546        }
1547    }
1548
1549    fn expr_type_to_comparison_type(expr_type: PbType) -> Result<PbAsOfJoinInequalityType> {
1550        match expr_type {
1551            PbType::LessThan => Ok(PbAsOfJoinInequalityType::AsOfInequalityTypeLt),
1552            PbType::LessThanOrEqual => Ok(PbAsOfJoinInequalityType::AsOfInequalityTypeLe),
1553            PbType::GreaterThan => Ok(PbAsOfJoinInequalityType::AsOfInequalityTypeGt),
1554            PbType::GreaterThanOrEqual => Ok(PbAsOfJoinInequalityType::AsOfInequalityTypeGe),
1555            _ => Err(ErrorCode::InvalidInputSyntax(format!(
1556                "Invalid comparison type: {}",
1557                expr_type.as_str_name()
1558            ))
1559            .into()),
1560        }
1561    }
1562}
1563
1564impl ToBatch for LogicalJoin {
1565    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
1566        let predicate = EqJoinPredicate::create(
1567            self.left().schema().len(),
1568            self.right().schema().len(),
1569            self.on().clone(),
1570        );
1571
1572        let batch_join = self
1573            .core
1574            .clone_with_inputs(self.core.left.to_batch()?, self.core.right.to_batch()?);
1575
1576        let ctx = self.base.ctx();
1577        let config = ctx.session_ctx().config();
1578
1579        if predicate.has_eq() {
1580            if !predicate.eq_keys_are_type_aligned() {
1581                return Err(ErrorCode::InternalError(format!(
1582                    "Join eq keys are not aligned for predicate: {predicate:?}"
1583                ))
1584                .into());
1585            }
1586            if config.batch_enable_lookup_join()
1587                && let Some(lookup_join) = self.to_batch_lookup_join_with_index_selection(
1588                    predicate.clone(),
1589                    batch_join.clone(),
1590                )?
1591            {
1592                return Ok(lookup_join.into());
1593            }
1594            self.to_batch_hash_join(batch_join, predicate)
1595        } else if self.is_asof_join() {
1596            Err(ErrorCode::InvalidInputSyntax(
1597                "AsOf join requires at least 1 equal condition".to_owned(),
1598            )
1599            .into())
1600        } else {
1601            // Convert to Nested-loop Join for non-equal joins
1602            Ok(BatchNestedLoopJoin::new(batch_join).into())
1603        }
1604    }
1605}
1606
1607impl ToStream for LogicalJoin {
1608    fn to_stream(
1609        &self,
1610        ctx: &mut ToStreamContext,
1611    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
1612        if self
1613            .on()
1614            .conjunctions
1615            .iter()
1616            .any(|cond| cond.count_nows() > 0)
1617        {
1618            return Err(ErrorCode::NotSupported(
1619                "optimizer has tried to separate the temporal predicate(with now() expression) from the on condition, but it still reminded in on join's condition. Considering move it into WHERE clause?".to_owned(),
1620                 "please refer to https://docs.risingwave.com/processing/sql/temporal-filters for more information".to_owned()).into());
1621        }
1622
1623        let predicate = EqJoinPredicate::create(
1624            self.left().schema().len(),
1625            self.right().schema().len(),
1626            self.on().clone(),
1627        );
1628
1629        if self.join_type() == JoinType::AsofInner || self.join_type() == JoinType::AsofLeftOuter {
1630            self.to_stream_asof_join(predicate, ctx)
1631        } else if predicate.has_eq() {
1632            if !predicate.eq_keys_are_type_aligned() {
1633                return Err(ErrorCode::InternalError(format!(
1634                    "Join eq keys are not aligned for predicate: {predicate:?}"
1635                ))
1636                .into());
1637            }
1638
1639            if let Some(scan) = self.should_be_stream_temporal_join(ctx)? {
1640                self.to_stream_temporal_join_with_index_selection(scan, predicate, ctx)
1641            } else {
1642                self.to_stream_hash_join(predicate, ctx)
1643            }
1644        } else if let Some(scan) = self.should_be_stream_temporal_join(ctx)? {
1645            self.to_stream_nested_loop_temporal_join(scan, predicate, ctx)
1646        } else if let Some(dynamic_filter) =
1647            self.to_stream_dynamic_filter(self.on().clone(), ctx)?
1648        {
1649            Ok(dynamic_filter)
1650        } else {
1651            Err(RwError::from(ErrorCode::NotSupported(
1652                "streaming nested-loop join".to_owned(),
1653                "The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. \
1654                 Consider rewriting the query to use dynamic filter as a substitute if possible.\n\
1655                 See also: https://docs.risingwave.com/processing/sql/dynamic-filters".to_owned(),
1656            )))
1657        }
1658    }
1659
1660    fn logical_rewrite_for_stream(
1661        &self,
1662        ctx: &mut RewriteStreamContext,
1663    ) -> Result<(PlanRef, ColIndexMapping)> {
1664        let eq_indexes = self.eq_indexes();
1665        let (logical_left, logical_right) = if eq_indexes.is_empty() {
1666            (self.left(), self.right())
1667        } else {
1668            let lhs_join_key_idx = eq_indexes.iter().map(|(l, _)| *l).collect_vec();
1669            if self.should_be_temporal_join() {
1670                (
1671                    try_enforce_locality_requirement(self.left(), &lhs_join_key_idx),
1672                    self.right(),
1673                )
1674            } else {
1675                let rhs_join_key_idx = eq_indexes.iter().map(|(_, r)| *r).collect_vec();
1676                (
1677                    try_enforce_locality_requirement(self.left(), &lhs_join_key_idx),
1678                    try_enforce_locality_requirement(self.right(), &rhs_join_key_idx),
1679                )
1680            }
1681        };
1682
1683        let (left, left_col_change) = logical_left.logical_rewrite_for_stream(ctx)?;
1684        let left_len = left.schema().len();
1685        let (right, right_col_change) = logical_right.logical_rewrite_for_stream(ctx)?;
1686        let (join, out_col_change) = self.rewrite_with_left_right(
1687            left.clone(),
1688            left_col_change,
1689            right.clone(),
1690            right_col_change,
1691        );
1692
1693        let mapping = ColIndexMapping::with_remaining_columns(
1694            join.output_indices(),
1695            join.internal_column_num(),
1696        );
1697
1698        let l2o = join.core.l2i_col_mapping().composite(&mapping);
1699        let r2o = join.core.r2i_col_mapping().composite(&mapping);
1700
1701        // Add missing pk indices to the logical join
1702        let mut left_to_add = left
1703            .expect_stream_key()
1704            .iter()
1705            .cloned()
1706            .filter(|i| l2o.try_map(*i).is_none())
1707            .collect_vec();
1708
1709        let mut right_to_add = right
1710            .expect_stream_key()
1711            .iter()
1712            .filter(|&&i| r2o.try_map(i).is_none())
1713            .map(|&i| i + left_len)
1714            .collect_vec();
1715
1716        // NOTE(st1page): add join keys in the pk_indices a work around before we really have stream
1717        // key.
1718        let right_len = right.schema().len();
1719        let eq_predicate = EqJoinPredicate::create(left_len, right_len, join.on().clone());
1720
1721        let either_or_both = self.core.add_which_join_key_to_pk();
1722
1723        for (lk, rk) in eq_predicate.eq_indexes() {
1724            match either_or_both {
1725                EitherOrBoth::Left(_) => {
1726                    if l2o.try_map(lk).is_none() {
1727                        left_to_add.push(lk);
1728                    }
1729                }
1730                EitherOrBoth::Right(_) => {
1731                    if r2o.try_map(rk).is_none() {
1732                        right_to_add.push(rk + left_len)
1733                    }
1734                }
1735                EitherOrBoth::Both(_, _) => {
1736                    if l2o.try_map(lk).is_none() {
1737                        left_to_add.push(lk);
1738                    }
1739                    if r2o.try_map(rk).is_none() {
1740                        right_to_add.push(rk + left_len)
1741                    }
1742                }
1743            };
1744        }
1745        let left_to_add = left_to_add.into_iter().unique();
1746        let right_to_add = right_to_add.into_iter().unique();
1747        // NOTE(st1page) over
1748
1749        let mut new_output_indices = join.output_indices().clone();
1750        if !join.is_right_join() {
1751            new_output_indices.extend(left_to_add);
1752        }
1753        if !join.is_left_join() {
1754            new_output_indices.extend(right_to_add);
1755        }
1756
1757        let join_with_pk = join.clone_with_output_indices(new_output_indices);
1758
1759        let plan = if join_with_pk.join_type() == JoinType::FullOuter {
1760            // ignore the all NULL to maintain the stream key's uniqueness, see https://github.com/risingwavelabs/risingwave/issues/8084 for more information
1761
1762            let l2o = join_with_pk
1763                .core
1764                .l2i_col_mapping()
1765                .composite(&join_with_pk.core.i2o_col_mapping());
1766            let r2o = join_with_pk
1767                .core
1768                .r2i_col_mapping()
1769                .composite(&join_with_pk.core.i2o_col_mapping());
1770            let mut left_right_keys = join_with_pk
1771                .left()
1772                .expect_stream_key()
1773                .iter()
1774                .map(|i| l2o.map(*i))
1775                .collect_vec();
1776            left_right_keys.extend(
1777                join_with_pk
1778                    .right()
1779                    .expect_stream_key()
1780                    .iter()
1781                    .map(|i| r2o.map(*i)),
1782            );
1783            left_right_keys.extend(
1784                eq_predicate
1785                    .eq_indexes()
1786                    .iter()
1787                    .flat_map(|(lk, rk)| [l2o.map(*lk), r2o.map(*rk)]),
1788            );
1789            let left_right_keys = left_right_keys.into_iter().unique().collect_vec();
1790            let plan: PlanRef = join_with_pk.into();
1791            LogicalFilter::filter_out_all_null_keys(plan, &left_right_keys)
1792        } else {
1793            join_with_pk.into()
1794        };
1795
1796        // the added columns is at the end, so it will not change the exists column index
1797        Ok((plan, out_col_change))
1798    }
1799
1800    fn try_better_locality(&self, columns: &[usize]) -> Option<PlanRef> {
1801        // Only propagate locality for temporal-filter.
1802        if !self.temporal_filter_candidate() {
1803            return None;
1804        }
1805
1806        // Temporal filter only outputs columns from left input, so mapping is safe.
1807        let o2i_mapping = self.core.o2i_col_mapping();
1808        let left_input_columns = columns
1809            .iter()
1810            .map(|&col| o2i_mapping.try_map(col))
1811            .collect::<Option<Vec<usize>>>()?;
1812        if let Some(better_left_plan) = self.left().try_better_locality(&left_input_columns) {
1813            return Some(
1814                self.clone_with_left_right(better_left_plan, self.right())
1815                    .into(),
1816            );
1817        }
1818        None
1819    }
1820}
1821
1822#[cfg(test)]
1823mod tests {
1824
1825    use std::collections::HashSet;
1826
1827    use risingwave_common::catalog::{Field, Schema};
1828    use risingwave_common::types::{DataType, Datum};
1829    use risingwave_pb::expr::expr_node::Type;
1830
1831    use super::*;
1832    use crate::expr::{FunctionCall, Literal, assert_eq_input_ref};
1833    use crate::optimizer::optimizer_context::OptimizerContext;
1834    use crate::optimizer::plan_node::LogicalValues;
1835    use crate::optimizer::property::FunctionalDependency;
1836
1837    /// Pruning
1838    /// ```text
1839    /// Join(on: input_ref(1)=input_ref(3))
1840    ///   TableScan(v1, v2, v3)
1841    ///   TableScan(v4, v5, v6)
1842    /// ```
1843    /// with required columns [2,3] will result in
1844    /// ```text
1845    /// Project(input_ref(1), input_ref(2))
1846    ///   Join(on: input_ref(0)=input_ref(2))
1847    ///     TableScan(v2, v3)
1848    ///     TableScan(v4)
1849    /// ```
1850    #[tokio::test]
1851    async fn test_prune_join() {
1852        let ty = DataType::Int32;
1853        let ctx = OptimizerContext::mock();
1854        let fields: Vec<Field> = (1..7)
1855            .map(|i| Field::with_name(ty.clone(), format!("v{}", i)))
1856            .collect();
1857        let left = LogicalValues::new(
1858            vec![],
1859            Schema {
1860                fields: fields[0..3].to_vec(),
1861            },
1862            ctx.clone(),
1863        );
1864        let right = LogicalValues::new(
1865            vec![],
1866            Schema {
1867                fields: fields[3..6].to_vec(),
1868            },
1869            ctx,
1870        );
1871        let on: ExprImpl = ExprImpl::FunctionCall(Box::new(
1872            FunctionCall::new(
1873                Type::Equal,
1874                vec![
1875                    ExprImpl::InputRef(Box::new(InputRef::new(1, ty.clone()))),
1876                    ExprImpl::InputRef(Box::new(InputRef::new(3, ty))),
1877                ],
1878            )
1879            .unwrap(),
1880        ));
1881        let join_type = JoinType::Inner;
1882        let join: PlanRef = LogicalJoin::new(
1883            left.into(),
1884            right.into(),
1885            join_type,
1886            Condition::with_expr(on),
1887        )
1888        .into();
1889
1890        // Perform the prune
1891        let required_cols = vec![2, 3];
1892        let plan = join.prune_col(&required_cols, &mut ColumnPruningContext::new(join.clone()));
1893
1894        // Check the result
1895        let join = plan.as_logical_join().unwrap();
1896        assert_eq!(join.schema().fields().len(), 2);
1897        assert_eq!(join.schema().fields()[0], fields[2]);
1898        assert_eq!(join.schema().fields()[1], fields[3]);
1899
1900        let expr: ExprImpl = join.on().clone().into();
1901        let call = expr.as_function_call().unwrap();
1902        assert_eq_input_ref!(&call.inputs()[0], 0);
1903        assert_eq_input_ref!(&call.inputs()[1], 2);
1904
1905        let left = join.left();
1906        let left = left.as_logical_values().unwrap();
1907        assert_eq!(left.schema().fields(), &fields[1..3]);
1908        let right = join.right();
1909        let right = right.as_logical_values().unwrap();
1910        assert_eq!(right.schema().fields(), &fields[3..4]);
1911    }
1912
1913    /// Semi join panicked previously at `prune_col`. Add test to prevent regression.
1914    #[tokio::test]
1915    async fn test_prune_semi_join() {
1916        let ty = DataType::Int32;
1917        let ctx = OptimizerContext::mock();
1918        let fields: Vec<Field> = (1..7)
1919            .map(|i| Field::with_name(ty.clone(), format!("v{}", i)))
1920            .collect();
1921        let left = LogicalValues::new(
1922            vec![],
1923            Schema {
1924                fields: fields[0..3].to_vec(),
1925            },
1926            ctx.clone(),
1927        );
1928        let right = LogicalValues::new(
1929            vec![],
1930            Schema {
1931                fields: fields[3..6].to_vec(),
1932            },
1933            ctx,
1934        );
1935        let on: ExprImpl = ExprImpl::FunctionCall(Box::new(
1936            FunctionCall::new(
1937                Type::Equal,
1938                vec![
1939                    ExprImpl::InputRef(Box::new(InputRef::new(1, ty.clone()))),
1940                    ExprImpl::InputRef(Box::new(InputRef::new(4, ty))),
1941                ],
1942            )
1943            .unwrap(),
1944        ));
1945        for join_type in [
1946            JoinType::LeftSemi,
1947            JoinType::RightSemi,
1948            JoinType::LeftAnti,
1949            JoinType::RightAnti,
1950        ] {
1951            let join = LogicalJoin::new(
1952                left.clone().into(),
1953                right.clone().into(),
1954                join_type,
1955                Condition::with_expr(on.clone()),
1956            );
1957
1958            let offset = if join.is_right_join() { 3 } else { 0 };
1959            let join: PlanRef = join.into();
1960            // Perform the prune
1961            let required_cols = vec![0];
1962            // key 0 is never used in the join (always key 1)
1963            let plan = join.prune_col(&required_cols, &mut ColumnPruningContext::new(join.clone()));
1964            let as_plan = plan.as_logical_join().unwrap();
1965            // Check the result
1966            assert_eq!(as_plan.schema().fields().len(), 1);
1967            assert_eq!(as_plan.schema().fields()[0], fields[offset]);
1968
1969            // Perform the prune
1970            let required_cols = vec![0, 1, 2];
1971            // should not panic here
1972            let plan = join.prune_col(&required_cols, &mut ColumnPruningContext::new(join.clone()));
1973            let as_plan = plan.as_logical_join().unwrap();
1974            // Check the result
1975            assert_eq!(as_plan.schema().fields().len(), 3);
1976            assert_eq!(as_plan.schema().fields()[0], fields[offset]);
1977            assert_eq!(as_plan.schema().fields()[1], fields[offset + 1]);
1978            assert_eq!(as_plan.schema().fields()[2], fields[offset + 2]);
1979        }
1980    }
1981
1982    /// Pruning
1983    /// ```text
1984    /// Join(on: input_ref(1)=input_ref(3))
1985    ///   TableScan(v1, v2, v3)
1986    ///   TableScan(v4, v5, v6)
1987    /// ```
1988    /// with required columns [1, 3] will result in
1989    /// ```text
1990    /// Join(on: input_ref(0)=input_ref(1))
1991    ///   TableScan(v2)
1992    ///   TableScan(v4)
1993    /// ```
1994    #[tokio::test]
1995    async fn test_prune_join_no_project() {
1996        let ty = DataType::Int32;
1997        let ctx = OptimizerContext::mock();
1998        let fields: Vec<Field> = (1..7)
1999            .map(|i| Field::with_name(ty.clone(), format!("v{}", i)))
2000            .collect();
2001        let left = LogicalValues::new(
2002            vec![],
2003            Schema {
2004                fields: fields[0..3].to_vec(),
2005            },
2006            ctx.clone(),
2007        );
2008        let right = LogicalValues::new(
2009            vec![],
2010            Schema {
2011                fields: fields[3..6].to_vec(),
2012            },
2013            ctx,
2014        );
2015        let on: ExprImpl = ExprImpl::FunctionCall(Box::new(
2016            FunctionCall::new(
2017                Type::Equal,
2018                vec![
2019                    ExprImpl::InputRef(Box::new(InputRef::new(1, ty.clone()))),
2020                    ExprImpl::InputRef(Box::new(InputRef::new(3, ty))),
2021                ],
2022            )
2023            .unwrap(),
2024        ));
2025        let join_type = JoinType::Inner;
2026        let join: PlanRef = LogicalJoin::new(
2027            left.into(),
2028            right.into(),
2029            join_type,
2030            Condition::with_expr(on),
2031        )
2032        .into();
2033
2034        // Perform the prune
2035        let required_cols = vec![1, 3];
2036        let plan = join.prune_col(&required_cols, &mut ColumnPruningContext::new(join.clone()));
2037
2038        // Check the result
2039        let join = plan.as_logical_join().unwrap();
2040        assert_eq!(join.schema().fields().len(), 2);
2041        assert_eq!(join.schema().fields()[0], fields[1]);
2042        assert_eq!(join.schema().fields()[1], fields[3]);
2043
2044        let expr: ExprImpl = join.on().clone().into();
2045        let call = expr.as_function_call().unwrap();
2046        assert_eq_input_ref!(&call.inputs()[0], 0);
2047        assert_eq_input_ref!(&call.inputs()[1], 1);
2048
2049        let left = join.left();
2050        let left = left.as_logical_values().unwrap();
2051        assert_eq!(left.schema().fields(), &fields[1..2]);
2052        let right = join.right();
2053        let right = right.as_logical_values().unwrap();
2054        assert_eq!(right.schema().fields(), &fields[3..4]);
2055    }
2056
2057    /// Convert
2058    /// ```text
2059    /// Join(on: ($1 = $3) AND ($2 == 42))
2060    ///   TableScan(v1, v2, v3)
2061    ///   TableScan(v4, v5, v6)
2062    /// ```
2063    /// to
2064    /// ```text
2065    /// Filter($2 == 42)
2066    ///   HashJoin(on: $1 = $3)
2067    ///     TableScan(v1, v2, v3)
2068    ///     TableScan(v4, v5, v6)
2069    /// ```
2070    #[tokio::test]
2071    async fn test_join_to_batch() {
2072        let ctx = OptimizerContext::mock();
2073        let fields: Vec<Field> = (1..7)
2074            .map(|i| Field::with_name(DataType::Int32, format!("v{}", i)))
2075            .collect();
2076        let left = LogicalValues::new(
2077            vec![],
2078            Schema {
2079                fields: fields[0..3].to_vec(),
2080            },
2081            ctx.clone(),
2082        );
2083        let right = LogicalValues::new(
2084            vec![],
2085            Schema {
2086                fields: fields[3..6].to_vec(),
2087            },
2088            ctx,
2089        );
2090
2091        fn input_ref(i: usize) -> ExprImpl {
2092            ExprImpl::InputRef(Box::new(InputRef::new(i, DataType::Int32)))
2093        }
2094        let eq_cond = ExprImpl::FunctionCall(Box::new(
2095            FunctionCall::new(Type::Equal, vec![input_ref(1), input_ref(3)]).unwrap(),
2096        ));
2097        let non_eq_cond = ExprImpl::FunctionCall(Box::new(
2098            FunctionCall::new(
2099                Type::Equal,
2100                vec![
2101                    input_ref(2),
2102                    ExprImpl::Literal(Box::new(Literal::new(
2103                        Datum::Some(42_i32.into()),
2104                        DataType::Int32,
2105                    ))),
2106                ],
2107            )
2108            .unwrap(),
2109        ));
2110        // Condition: ($1 = $3) AND ($2 == 42)
2111        let on_cond = ExprImpl::FunctionCall(Box::new(
2112            FunctionCall::new(Type::And, vec![eq_cond.clone(), non_eq_cond.clone()]).unwrap(),
2113        ));
2114
2115        let join_type = JoinType::Inner;
2116        let logical_join = LogicalJoin::new(
2117            left.into(),
2118            right.into(),
2119            join_type,
2120            Condition::with_expr(on_cond),
2121        );
2122
2123        // Perform `to_batch`
2124        let result = logical_join.to_batch().unwrap();
2125
2126        // Expected plan:  HashJoin($1 = $3 AND $2 == 42)
2127        let hash_join = result.as_batch_hash_join().unwrap();
2128        assert_eq!(
2129            ExprImpl::from(hash_join.eq_join_predicate().eq_cond()),
2130            eq_cond
2131        );
2132        assert_eq!(
2133            *hash_join
2134                .eq_join_predicate()
2135                .non_eq_cond()
2136                .conjunctions
2137                .first()
2138                .unwrap(),
2139            non_eq_cond
2140        );
2141    }
2142
2143    /// Convert
2144    /// ```text
2145    /// Join(join_type: left outer, on: ($1 = $3) AND ($2 == 42))
2146    ///   TableScan(v1, v2, v3)
2147    ///   TableScan(v4, v5, v6)
2148    /// ```
2149    /// to
2150    /// ```text
2151    /// HashJoin(join_type: left outer, on: ($1 = $3) AND ($2 == 42))
2152    ///   TableScan(v1, v2, v3)
2153    ///   TableScan(v4, v5, v6)
2154    /// ```
2155    #[tokio::test]
2156    #[ignore] // ignore due to refactor logical scan, but the test seem to duplicate with the explain test
2157    // framework, maybe we will remove it?
2158    async fn test_join_to_stream() {
2159        // let ctx = Rc::new(RefCell::new(QueryContext::mock().await));
2160        // let fields: Vec<Field> = (1..7)
2161        //     .map(|i| Field {
2162        //         data_type: DataType::Int32,
2163        //         name: format!("v{}", i),
2164        //     })
2165        //     .collect();
2166        // let left = LogicalScan::new(
2167        //     "left".to_string(),
2168        //     TableId::new(0),
2169        //     vec![1.into(), 2.into(), 3.into()],
2170        //     Schema {
2171        //         fields: fields[0..3].to_vec(),
2172        //     },
2173        //     ctx.clone(),
2174        // );
2175        // let right = LogicalScan::new(
2176        //     "right".to_string(),
2177        //     TableId::new(0),
2178        //     vec![4.into(), 5.into(), 6.into()],
2179        //     Schema {
2180        //                 fields: fields[3..6].to_vec(),
2181        //     },
2182        //     ctx,
2183        // );
2184        // let eq_cond = ExprImpl::FunctionCall(Box::new(
2185        //     FunctionCall::new(
2186        //         Type::Equal,
2187        //         vec![
2188        //             ExprImpl::InputRef(Box::new(InputRef::new(1, DataType::Int32))),
2189        //             ExprImpl::InputRef(Box::new(InputRef::new(3, DataType::Int32))),
2190        //         ],
2191        //     )
2192        //     .unwrap(),
2193        // ));
2194        // let non_eq_cond = ExprImpl::FunctionCall(Box::new(
2195        //     FunctionCall::new(
2196        //         Type::Equal,
2197        //         vec![
2198        //             ExprImpl::InputRef(Box::new(InputRef::new(2, DataType::Int32))),
2199        //             ExprImpl::Literal(Box::new(Literal::new(
2200        //                 Datum::Some(42_i32.into()),
2201        //                 DataType::Int32,
2202        //             ))),
2203        //         ],
2204        //     )
2205        //     .unwrap(),
2206        // ));
2207        // // Condition: ($1 = $3) AND ($2 == 42)
2208        // let on_cond = ExprImpl::FunctionCall(Box::new(
2209        //     FunctionCall::new(Type::And, vec![eq_cond, non_eq_cond]).unwrap(),
2210        // ));
2211
2212        // let join_type = JoinType::LeftOuter;
2213        // let logical_join = LogicalJoin::new(
2214        //     left.clone().into(),
2215        //     right.clone().into(),
2216        //     join_type,
2217        //     Condition::with_expr(on_cond.clone()),
2218        // );
2219
2220        // // Perform `to_stream`
2221        // let result = logical_join.to_stream();
2222
2223        // // Expected plan: HashJoin(($1 = $3) AND ($2 == 42))
2224        // let hash_join = result.as_stream_hash_join().unwrap();
2225        // assert_eq!(hash_join.eq_join_predicate().all_cond().as_expr(), on_cond);
2226    }
2227    /// Pruning
2228    /// ```text
2229    /// Join(on: input_ref(1)=input_ref(3))
2230    ///   TableScan(v1, v2, v3)
2231    ///   TableScan(v4, v5, v6)
2232    /// ```
2233    /// with required columns [3, 2] will result in
2234    /// ```text
2235    /// Project(input_ref(2), input_ref(1))
2236    ///   Join(on: input_ref(0)=input_ref(2))
2237    ///     TableScan(v2, v3)
2238    ///     TableScan(v4)
2239    /// ```
2240    #[tokio::test]
2241    async fn test_join_column_prune_with_order_required() {
2242        let ty = DataType::Int32;
2243        let ctx = OptimizerContext::mock();
2244        let fields: Vec<Field> = (1..7)
2245            .map(|i| Field::with_name(ty.clone(), format!("v{}", i)))
2246            .collect();
2247        let left = LogicalValues::new(
2248            vec![],
2249            Schema {
2250                fields: fields[0..3].to_vec(),
2251            },
2252            ctx.clone(),
2253        );
2254        let right = LogicalValues::new(
2255            vec![],
2256            Schema {
2257                fields: fields[3..6].to_vec(),
2258            },
2259            ctx,
2260        );
2261        let on: ExprImpl = ExprImpl::FunctionCall(Box::new(
2262            FunctionCall::new(
2263                Type::Equal,
2264                vec![
2265                    ExprImpl::InputRef(Box::new(InputRef::new(1, ty.clone()))),
2266                    ExprImpl::InputRef(Box::new(InputRef::new(3, ty))),
2267                ],
2268            )
2269            .unwrap(),
2270        ));
2271        let join_type = JoinType::Inner;
2272        let join: PlanRef = LogicalJoin::new(
2273            left.into(),
2274            right.into(),
2275            join_type,
2276            Condition::with_expr(on),
2277        )
2278        .into();
2279
2280        // Perform the prune
2281        let required_cols = vec![3, 2];
2282        let plan = join.prune_col(&required_cols, &mut ColumnPruningContext::new(join.clone()));
2283
2284        // Check the result
2285        let join = plan.as_logical_join().unwrap();
2286        assert_eq!(join.schema().fields().len(), 2);
2287        assert_eq!(join.schema().fields()[0], fields[3]);
2288        assert_eq!(join.schema().fields()[1], fields[2]);
2289
2290        let expr: ExprImpl = join.on().clone().into();
2291        let call = expr.as_function_call().unwrap();
2292        assert_eq_input_ref!(&call.inputs()[0], 0);
2293        assert_eq_input_ref!(&call.inputs()[1], 2);
2294
2295        let left = join.left();
2296        let left = left.as_logical_values().unwrap();
2297        assert_eq!(left.schema().fields(), &fields[1..3]);
2298        let right = join.right();
2299        let right = right.as_logical_values().unwrap();
2300        assert_eq!(right.schema().fields(), &fields[3..4]);
2301    }
2302
2303    #[tokio::test]
2304    async fn fd_derivation_inner_outer_join() {
2305        // left: [l0, l1], right: [r0, r1, r2]
2306        // FD: l0 --> l1, r0 --> { r1, r2 }
2307        // On: l0 = 0 AND l1 = r1
2308        //
2309        // Inner Join:
2310        //  Schema: [l0, l1, r0, r1, r2]
2311        //  FD: l0 --> l1, r0 --> { r1, r2 }, {} --> l0, l1 --> r1, r1 --> l1
2312        // Left Outer Join:
2313        //  Schema: [l0, l1, r0, r1, r2]
2314        //  FD: l0 --> l1
2315        // Right Outer Join:
2316        //  Schema: [l0, l1, r0, r1, r2]
2317        //  FD: r0 --> { r1, r2 }
2318        // Full Outer Join:
2319        //  Schema: [l0, l1, r0, r1, r2]
2320        //  FD: empty
2321        // Left Semi/Anti Join:
2322        //  Schema: [l0, l1]
2323        //  FD: l0 --> l1
2324        // Right Semi/Anti Join:
2325        //  Schema: [r0, r1, r2]
2326        //  FD: r0 --> {r1, r2}
2327        let ctx = OptimizerContext::mock();
2328        let left = {
2329            let fields: Vec<Field> = vec![
2330                Field::with_name(DataType::Int32, "l0"),
2331                Field::with_name(DataType::Int32, "l1"),
2332            ];
2333            let mut values = LogicalValues::new(vec![], Schema { fields }, ctx.clone());
2334            // 0 --> 1
2335            values
2336                .base
2337                .functional_dependency_mut()
2338                .add_functional_dependency_by_column_indices(&[0], &[1]);
2339            values
2340        };
2341        let right = {
2342            let fields: Vec<Field> = vec![
2343                Field::with_name(DataType::Int32, "r0"),
2344                Field::with_name(DataType::Int32, "r1"),
2345                Field::with_name(DataType::Int32, "r2"),
2346            ];
2347            let mut values = LogicalValues::new(vec![], Schema { fields }, ctx);
2348            // 0 --> 1, 2
2349            values
2350                .base
2351                .functional_dependency_mut()
2352                .add_functional_dependency_by_column_indices(&[0], &[1, 2]);
2353            values
2354        };
2355        // l0 = 0 AND l1 = r1
2356        let on: ExprImpl = FunctionCall::new(
2357            Type::And,
2358            vec![
2359                FunctionCall::new(
2360                    Type::Equal,
2361                    vec![
2362                        InputRef::new(0, DataType::Int32).into(),
2363                        ExprImpl::literal_int(0),
2364                    ],
2365                )
2366                .unwrap()
2367                .into(),
2368                FunctionCall::new(
2369                    Type::Equal,
2370                    vec![
2371                        InputRef::new(1, DataType::Int32).into(),
2372                        InputRef::new(3, DataType::Int32).into(),
2373                    ],
2374                )
2375                .unwrap()
2376                .into(),
2377            ],
2378        )
2379        .unwrap()
2380        .into();
2381        let expected_fd_set = [
2382            (
2383                JoinType::Inner,
2384                [
2385                    // inherit from left
2386                    FunctionalDependency::with_indices(5, &[0], &[1]),
2387                    // inherit from right
2388                    FunctionalDependency::with_indices(5, &[2], &[3, 4]),
2389                    // constant column in join condition
2390                    FunctionalDependency::with_indices(5, &[], &[0]),
2391                    // eq column in join condition
2392                    FunctionalDependency::with_indices(5, &[1], &[3]),
2393                    FunctionalDependency::with_indices(5, &[3], &[1]),
2394                ]
2395                .into_iter()
2396                .collect::<HashSet<_>>(),
2397            ),
2398            (JoinType::FullOuter, HashSet::new()),
2399            (
2400                JoinType::RightOuter,
2401                [
2402                    // inherit from right
2403                    FunctionalDependency::with_indices(5, &[2], &[3, 4]),
2404                ]
2405                .into_iter()
2406                .collect::<HashSet<_>>(),
2407            ),
2408            (
2409                JoinType::LeftOuter,
2410                [
2411                    // inherit from left
2412                    FunctionalDependency::with_indices(5, &[0], &[1]),
2413                ]
2414                .into_iter()
2415                .collect::<HashSet<_>>(),
2416            ),
2417            (
2418                JoinType::LeftSemi,
2419                [
2420                    // inherit from left
2421                    FunctionalDependency::with_indices(2, &[0], &[1]),
2422                ]
2423                .into_iter()
2424                .collect::<HashSet<_>>(),
2425            ),
2426            (
2427                JoinType::LeftAnti,
2428                [
2429                    // inherit from left
2430                    FunctionalDependency::with_indices(2, &[0], &[1]),
2431                ]
2432                .into_iter()
2433                .collect::<HashSet<_>>(),
2434            ),
2435            (
2436                JoinType::RightSemi,
2437                [
2438                    // inherit from right
2439                    FunctionalDependency::with_indices(3, &[0], &[1, 2]),
2440                ]
2441                .into_iter()
2442                .collect::<HashSet<_>>(),
2443            ),
2444            (
2445                JoinType::RightAnti,
2446                [
2447                    // inherit from right
2448                    FunctionalDependency::with_indices(3, &[0], &[1, 2]),
2449                ]
2450                .into_iter()
2451                .collect::<HashSet<_>>(),
2452            ),
2453        ];
2454
2455        for (join_type, expected_res) in expected_fd_set {
2456            let join = LogicalJoin::new(
2457                left.clone().into(),
2458                right.clone().into(),
2459                join_type,
2460                Condition::with_expr(on.clone()),
2461            );
2462            let fd_set = join
2463                .functional_dependency()
2464                .as_dependencies()
2465                .iter()
2466                .cloned()
2467                .collect::<HashSet<_>>();
2468            assert_eq!(fd_set, expected_res);
2469        }
2470    }
2471}