risingwave_frontend/optimizer/plan_node/
predicate_pushdown.rs1use std::collections::{HashMap, HashSet};
16
17use super::*;
18use crate::optimizer::plan_visitor::{ExprCorrelatedIdFinder, ShareParentCounter};
19use crate::optimizer::{
20 ExpressionSimplifyRewriter, LogicalPlanRef as PlanRef, PlanVisitor, ShareId,
21};
22
23pub trait PredicatePushdown {
26 fn predicate_pushdown(
50 &self,
51 predicate: Condition,
52 ctx: &mut PredicatePushdownContext,
53 ) -> PlanRef;
54}
55
56#[inline]
57pub fn gen_filter_and_pushdown<T: PlanTreeNodeUnary<Logical> + LogicalPlanNode>(
58 node: &T,
59 filter_predicate: Condition,
60 pushed_predicate: Condition,
61 ctx: &mut PredicatePushdownContext,
62) -> PlanRef {
63 let new_input = node.input().predicate_pushdown(pushed_predicate, ctx);
64 let new_node = node.clone_with_input(new_input);
65 LogicalFilter::create(new_node.into(), filter_predicate)
66}
67
68fn merge_share_predicates(requirements: Vec<Condition>) -> Condition {
69 let merged = requirements
70 .into_iter()
71 .map(|mut condition| Condition {
72 conjunctions: condition
73 .conjunctions
74 .extract_if(.., |expr| {
75 let mut finder = ExprCorrelatedIdFinder::default();
78 finder.visit_expr(expr);
79 expr.count_nows() == 0 && expr.is_pure() && !finder.has_correlated_input_ref()
80 })
81 .collect(),
82 })
83 .reduce(Condition::or)
84 .expect("a shared plan must have at least one parent predicate");
85
86 let mut rewriter = ExpressionSimplifyRewriter {};
87 merged
88 .conjunctions
89 .into_iter()
90 .fold(Condition::true_cond(), |condition, expr| {
91 condition.and(Condition::with_expr(rewriter.rewrite_cond(expr)))
92 })
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96enum PredicatePushdownPhase {
97 Idle,
98 Collect,
99 Rewrite,
100}
101
102#[derive(Debug, Clone)]
103struct SharePredicatePushdown {
104 original_input: PlanRef,
105 predicate: Condition,
106}
107
108#[derive(Debug, Clone)]
109pub struct PredicatePushdownContext {
110 pending_predicates: HashMap<ShareId, Vec<Condition>>,
111 collected_shares: HashMap<ShareId, SharePredicatePushdown>,
112 share_parent_counter: ShareParentCounter,
113 rebuilt_shares: HashSet<ShareId>,
114 skipped_shares: HashSet<ShareId>,
115 phase: PredicatePushdownPhase,
116}
117
118impl PredicatePushdownContext {
119 pub fn new(root: PlanRef) -> Self {
120 let mut share_parent_counter = ShareParentCounter::default();
121 share_parent_counter.visit(root);
122 Self {
123 pending_predicates: HashMap::new(),
124 collected_shares: HashMap::new(),
125 share_parent_counter,
126 rebuilt_shares: HashSet::new(),
127 skipped_shares: HashSet::new(),
128 phase: PredicatePushdownPhase::Idle,
129 }
130 }
131
132 pub(in crate::optimizer) fn is_running(&self) -> bool {
133 self.phase != PredicatePushdownPhase::Idle
134 }
135
136 pub(in crate::optimizer) fn is_collecting(&self) -> bool {
137 self.phase == PredicatePushdownPhase::Collect
138 }
139
140 pub(in crate::optimizer) fn get_parent_num(&self, share: &LogicalShare) -> usize {
141 self.share_parent_counter.get_parent_num(share)
142 }
143
144 pub(in crate::optimizer) fn add_predicate(
145 &mut self,
146 share: &LogicalShare,
147 predicate: Condition,
148 ) -> Option<Condition> {
149 let share_id = share.share_id();
150 let parent_num = self.share_parent_counter.get_parent_num_by_id(share_id);
151 let pending = self.pending_predicates.entry(share_id).or_default();
152 pending.push(predicate);
153 assert!(
154 pending.len() <= parent_num,
155 "share {share_id:?} received more predicates than parents"
156 );
157 if pending.len() != parent_num {
158 return None;
159 }
160
161 let merged_predicate = merge_share_predicates(
162 self.pending_predicates
163 .remove(&share_id)
164 .expect("share predicates must exist"),
165 );
166 self.collected_shares
167 .try_insert(
168 share_id,
169 SharePredicatePushdown {
170 original_input: share.input(),
171 predicate: merged_predicate.clone(),
172 },
173 )
174 .expect("predicates must be merged once per share");
175 Some(merged_predicate)
176 }
177
178 pub(in crate::optimizer) fn ensure_share_rebuilt(&mut self, share: &LogicalShare) {
181 let share_id = share.share_id();
182 if self.rebuilt_shares.contains(&share_id) {
183 return;
184 }
185 assert_eq!(self.phase, PredicatePushdownPhase::Rewrite);
186
187 let Some(SharePredicatePushdown {
188 original_input,
189 predicate,
190 }) = self.collected_shares.remove(&share_id)
191 else {
192 assert!(
196 self.skipped_shares.contains(&share_id),
197 "share {share_id:?} has no collected predicates"
198 );
199 return;
200 };
201 let rebuilt_input = original_input.predicate_pushdown(predicate, self);
202 share.ctx().update_logical_share(share_id, rebuilt_input);
203 assert!(self.rebuilt_shares.insert(share_id));
204 }
205
206 pub(in crate::optimizer) fn run(&mut self, root: PlanRef, predicate: Condition) -> PlanRef {
207 self.phase = PredicatePushdownPhase::Collect;
208 let collected = root.predicate_pushdown_inner(predicate.clone(), self);
209 debug_assert!(
217 self.pending_predicates.is_empty(),
218 "shares {:?} did not receive predicates from every parent; some \
219 `PredicatePushdown` impl likely failed to recurse into an input — push \
220 `Condition::true_cond()` instead of returning early",
221 self.pending_predicates.keys().collect::<Vec<_>>(),
222 );
223 self.skipped_shares.extend(
224 self.pending_predicates
225 .drain()
226 .map(|(share_id, _)| share_id),
227 );
228 if self.collected_shares.is_empty() {
229 self.phase = PredicatePushdownPhase::Idle;
230 return collected;
231 }
232
233 self.phase = PredicatePushdownPhase::Rewrite;
234 let result = root.predicate_pushdown_inner(predicate, self);
235 self.phase = PredicatePushdownPhase::Idle;
236 result
237 }
238}