risingwave_frontend/optimizer/plan_node/
logical_share.rs1use pretty_xmlish::{Pretty, XmlNode};
16use risingwave_common::bail_not_implemented;
17
18use super::utils::{Distill, childless_record};
19use super::{
20 ColPrunable, ExprRewritable, Logical, LogicalPlanRef as PlanRef, PlanBase, PlanTreeNodeUnary,
21 PredicatePushdown, ShareNode, StreamPlanRef, ToBatch, ToStream, generic,
22};
23use crate::error::Result;
24use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
25use crate::optimizer::plan_node::generic::{GenericPlanRef, Share};
26use crate::optimizer::plan_node::{
27 ColumnPruningContext, PredicatePushdownContext, RewriteStreamContext, StreamShare,
28 ToStreamContext,
29};
30use crate::optimizer::{OptimizerContextRef, ShareId};
31use crate::utils::{ColIndexMapping, Condition};
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
51pub struct LogicalShare {
52 pub base: PlanBase<Logical>,
53 core: generic::Share<PlanRef>,
54}
55
56impl LogicalShare {
57 pub fn new(input: PlanRef) -> Self {
58 let ctx = input.ctx();
59 let core = ctx.register_logical_share(input);
60 Self::with_core(core)
61 }
62
63 fn with_core(core: generic::Share<PlanRef>) -> Self {
64 let base = PlanBase::new_logical_share(&core);
65 LogicalShare { base, core }
66 }
67
68 pub(in crate::optimizer) fn from_share_id(ctx: OptimizerContextRef, share_id: ShareId) -> Self {
69 Self::with_core(ctx.logical_share(share_id))
70 }
71
72 pub fn create(input: PlanRef) -> PlanRef {
73 LogicalShare::new(input).into()
74 }
75
76 pub(super) fn pretty_fields(base: impl GenericPlanRef, name: &str) -> XmlNode<'_> {
77 childless_record(name, vec![("id", Pretty::debug(&base.id().0))])
78 }
79}
80
81impl PlanTreeNodeUnary<Logical> for LogicalShare {
82 fn input(&self) -> PlanRef {
83 self.core.input()
84 }
85
86 fn clone_with_input(&self, _input: PlanRef) -> Self {
87 unreachable!("shared node should be handled specially in PlanRef::clone_with_input")
88 }
89
90 fn rewrite_with_input(
91 &self,
92 input: PlanRef,
93 input_col_change: ColIndexMapping,
94 ) -> (Self, ColIndexMapping) {
95 (Self::new(input), input_col_change)
96 }
97}
98
99impl_plan_tree_node_for_unary! { Logical, LogicalShare}
100
101impl ShareNode<Logical> for LogicalShare {
102 fn share_id(&self) -> ShareId {
103 self.core.share_id()
104 }
105
106 fn new_share(core: Share<PlanRef>) -> PlanRef {
107 Self::with_core(core).into()
108 }
109
110 fn replace_input(&self, plan: PlanRef) -> PlanRef {
111 debug_assert!(
112 self.schema().type_eq(plan.schema()),
113 "replacing a logical share input must preserve its schema"
114 );
115 self.ctx()
116 .update_logical_share(self.share_id(), plan.clone());
117 Self::with_core(self.core.with_input(plan)).into()
118 }
119
120 fn fork_with_input(&self, plan: PlanRef) -> PlanRef {
121 Self::new(plan).into()
122 }
123}
124
125impl Distill for LogicalShare {
126 fn distill<'a>(&self) -> XmlNode<'a> {
127 Self::pretty_fields(&self.base, "LogicalShare")
128 }
129}
130
131impl ColPrunable for LogicalShare {
132 fn prune_col(&self, _required_cols: &[usize], _ctx: &mut ColumnPruningContext) -> PlanRef {
133 unimplemented!("call prune_col of the PlanRef instead of calling directly on LogicalShare")
134 }
135}
136
137impl ExprRewritable<Logical> for LogicalShare {}
138
139impl ExprVisitable for LogicalShare {}
140
141impl PredicatePushdown for LogicalShare {
142 fn predicate_pushdown(
143 &self,
144 _predicate: Condition,
145 _ctx: &mut PredicatePushdownContext,
146 ) -> PlanRef {
147 unimplemented!(
148 "call predicate_pushdown of the PlanRef instead of calling directly on LogicalShare"
149 )
150 }
151}
152
153impl ToBatch for LogicalShare {
154 fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
155 bail_not_implemented!("batch query doesn't support share operator for now");
156 }
157}
158
159impl ToStream for LogicalShare {
160 fn to_stream(
161 &self,
162 ctx: &mut ToStreamContext,
163 ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
164 match ctx.get_to_stream_result(self.share_id()) {
165 None => {
166 let new_input = self.input().to_stream(ctx)?;
167 let stream_share_ref: StreamPlanRef = StreamShare::new_from_input(new_input).into();
168 ctx.add_to_stream_result(self.share_id(), stream_share_ref.clone());
169 Ok(stream_share_ref)
170 }
171 Some(cache) => Ok(cache.clone()),
172 }
173 }
174
175 fn logical_rewrite_for_stream(
176 &self,
177 ctx: &mut RewriteStreamContext,
178 ) -> Result<(PlanRef, ColIndexMapping)> {
179 match ctx.get_rewrite_result(self.share_id()) {
180 None => {
181 let (new_input, col_change) = self.input().logical_rewrite_for_stream(ctx)?;
182 let new_share: PlanRef = Self::new(new_input).into();
183 ctx.add_rewrite_result(self.share_id(), new_share.clone(), col_change.clone());
184 Ok((new_share, col_change))
185 }
186 Some(cache) => Ok(cache.clone()),
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193
194 use risingwave_common::catalog::{Field, Schema};
195 use risingwave_common::types::{DataType, ScalarImpl};
196 use risingwave_pb::expr::expr_node::Type;
197 use risingwave_pb::plan_common::JoinType;
198
199 use super::*;
200 use crate::expr::{ExprImpl, FunctionCall, InputRef, Literal};
201 use crate::optimizer::optimizer_context::OptimizerContext;
202 use crate::optimizer::plan_node::{
203 LogicalFilter, LogicalJoin, LogicalValues, PlanTreeNodeBinary,
204 };
205
206 #[tokio::test]
207 async fn test_share_predicate_pushdown() {
208 let ty = DataType::Int32;
209 let ctx = OptimizerContext::mock();
210 let fields: Vec<Field> = vec![
211 Field::with_name(ty.clone(), "v1"),
212 Field::with_name(ty.clone(), "v2"),
213 Field::with_name(ty.clone(), "v3"),
214 ];
215 let values1 = LogicalValues::new(vec![], Schema { fields }, ctx);
216
217 let share: PlanRef = LogicalShare::create(values1.into());
218
219 let on: ExprImpl = ExprImpl::FunctionCall(Box::new(
220 FunctionCall::new(
221 Type::Equal,
222 vec![
223 ExprImpl::InputRef(Box::new(InputRef::new(1, ty.clone()))),
224 ExprImpl::InputRef(Box::new(InputRef::new(3, ty))),
225 ],
226 )
227 .unwrap(),
228 ));
229
230 let predicate1: ExprImpl = ExprImpl::FunctionCall(Box::new(
231 FunctionCall::new(
232 Type::Equal,
233 vec![
234 ExprImpl::InputRef(Box::new(InputRef::new(0, DataType::Int32))),
235 ExprImpl::Literal(Box::new(Literal::new(
236 Some(ScalarImpl::from(100)),
237 DataType::Int32,
238 ))),
239 ],
240 )
241 .unwrap(),
242 ));
243
244 let predicate2: ExprImpl = ExprImpl::FunctionCall(Box::new(
245 FunctionCall::new(
246 Type::Equal,
247 vec![
248 ExprImpl::InputRef(Box::new(InputRef::new(4, DataType::Int32))),
249 ExprImpl::Literal(Box::new(Literal::new(
250 Some(ScalarImpl::from(200)),
251 DataType::Int32,
252 ))),
253 ],
254 )
255 .unwrap(),
256 ));
257
258 let join: PlanRef = LogicalJoin::create(share.clone(), share, JoinType::Inner, on);
259
260 let filter1: PlanRef = LogicalFilter::create_with_expr(join, predicate1);
261
262 let filter2: PlanRef = LogicalFilter::create_with_expr(filter1, predicate2);
263
264 let result = filter2.predicate_pushdown(
265 Condition::true_cond(),
266 &mut PredicatePushdownContext::new(filter2.clone()),
267 );
268
269 let logical_join: &LogicalJoin = result.as_logical_join().unwrap();
280 let left = logical_join.left();
281 let left_filter: &LogicalFilter = left.as_logical_filter().unwrap();
282 let left_filter_input = left_filter.input();
283 let logical_share: &LogicalShare = left_filter_input.as_logical_share().unwrap();
284 let share_input = logical_share.input();
285 let share_input_filter: &LogicalFilter = share_input.as_logical_filter().unwrap();
286 let disjunctions = share_input_filter.predicate().conjunctions[0]
287 .as_or_disjunctions()
288 .unwrap();
289 assert_eq!(disjunctions.len(), 2);
290 let (input_ref1, _const1) = disjunctions[0].as_eq_const().unwrap();
291 let (input_ref2, _const2) = disjunctions[1].as_eq_const().unwrap();
292 if input_ref1.index() == 0 {
293 assert_eq!(input_ref2.index(), 1);
294 } else {
295 assert_eq!(input_ref1.index(), 1);
296 assert_eq!(input_ref2.index(), 0);
297 }
298 }
299}