risingwave_frontend/optimizer/plan_visitor/
cardinality_visitor.rs1use std::collections::HashSet;
16use std::ops::{Mul, Sub};
17
18use itertools::Itertools;
19use risingwave_common::types::DataType;
20use risingwave_common::util::iter_util::ZipEqFast;
21use risingwave_pb::plan_common::JoinType;
22
23use super::{DefaultBehavior, DefaultValue, LogicalPlanVisitor, PlanVisitor};
24use crate::expr::{Expr, ExprImpl, ExprType};
25use crate::optimizer::plan_node::generic::TopNLimit;
26use crate::optimizer::plan_node::{
27 self, LogicalPlanRef as PlanRef, PlanTreeNode, PlanTreeNodeBinary, PlanTreeNodeUnary,
28};
29use crate::optimizer::plan_visitor::LogicalPlanNode;
30use crate::optimizer::property::Cardinality;
31use crate::utils::Condition;
32
33pub struct CardinalityVisitor;
35
36impl CardinalityVisitor {
37 fn visit_predicate(
39 input: &dyn LogicalPlanNode,
40 input_card: Cardinality,
41 eq_set: HashSet<usize>,
42 ) -> Cardinality {
43 let unique_keys: Vec<HashSet<_>> = input
45 .stream_key()
46 .into_iter()
47 .map(|s| s.iter().copied().collect())
48 .collect();
49
50 if unique_keys
51 .iter()
52 .any(|unique_key| eq_set.is_superset(unique_key))
53 {
54 input_card.min(0..=1)
55 } else {
56 input_card.min(0..)
57 }
58 }
59
60 fn fixed_columns(predicate: &Condition) -> HashSet<usize> {
71 let mut fixed = HashSet::new();
72 for conjunction in &predicate.conjunctions {
73 match conjunction {
74 ExprImpl::InputRef(input_ref) => {
75 fixed.insert(input_ref.index());
76 }
77 ExprImpl::FunctionCall(call) => match (call.func_type(), call.inputs()) {
78 (
79 ExprType::Not | ExprType::IsTrue | ExprType::IsFalse,
80 [ExprImpl::InputRef(input_ref)],
81 ) => {
82 fixed.insert(input_ref.index());
83 }
84 (ExprType::Equal | ExprType::IsNotDistinctFrom | ExprType::In, [lhs, rhs]) => {
86 Self::collect_equality_fixed_columns(lhs, rhs, &mut fixed);
87 }
88 _ => {}
89 },
90 _ => {}
91 }
92 }
93 fixed
94 }
95
96 fn collect_equality_fixed_columns(lhs: &ExprImpl, rhs: &ExprImpl, fixed: &mut HashSet<usize>) {
99 if let (Some(lhs_fields), Some(rhs_fields)) = (Self::row_fields(lhs), Self::row_fields(rhs))
100 && lhs_fields.len() == rhs_fields.len()
101 {
102 for (lhs, rhs) in lhs_fields.iter().zip_eq_fast(rhs_fields) {
103 Self::collect_equality_fixed_columns(lhs, rhs, fixed);
104 }
105 return;
106 }
107 let is_fixed_value = |e: &ExprImpl| !e.has_input_ref() && !e.has_subquery() && e.is_pure();
108 if is_fixed_value(rhs) {
109 Self::collect_operand_columns(lhs, fixed);
110 } else if is_fixed_value(lhs) {
111 Self::collect_operand_columns(rhs, fixed);
112 }
113 }
114
115 fn collect_operand_columns(expr: &ExprImpl, fixed: &mut HashSet<usize>) {
118 if let Some(fields) = Self::row_fields(expr) {
119 for field in fields {
120 Self::collect_operand_columns(field, fixed);
121 }
122 } else if let Some(index) = Self::as_injective_input_ref(expr) {
123 fixed.insert(index);
124 }
125 }
126
127 fn row_fields(expr: &ExprImpl) -> Option<&[ExprImpl]> {
128 match expr {
129 ExprImpl::FunctionCall(call) if call.func_type() == ExprType::Row => {
130 Some(call.inputs())
131 }
132 _ => None,
133 }
134 }
135
136 fn as_injective_input_ref(expr: &ExprImpl) -> Option<usize> {
141 match expr {
142 ExprImpl::InputRef(input_ref) => Some(input_ref.index()),
143 ExprImpl::FunctionCall(call) if call.func_type() == ExprType::Cast => {
144 let [input] = call.inputs() else {
145 return None;
146 };
147 let lossless = matches!(
148 (input.return_type(), call.return_type()),
149 (
150 DataType::Int16,
151 DataType::Int32
152 | DataType::Int64
153 | DataType::Decimal
154 | DataType::Float32
155 | DataType::Float64
156 ) | (
157 DataType::Int32,
158 DataType::Int64 | DataType::Decimal | DataType::Float64
159 ) | (DataType::Int64, DataType::Decimal)
160 );
161 if lossless {
162 Self::as_injective_input_ref(input)
163 } else {
164 None
165 }
166 }
167 _ => None,
168 }
169 }
170}
171
172impl LogicalPlanVisitor for CardinalityVisitor {
173 type Result = Cardinality;
174
175 type DefaultBehavior = impl DefaultBehavior<Self::Result>;
176
177 fn default_behavior() -> Self::DefaultBehavior {
178 DefaultValue
180 }
181
182 fn visit_logical_values(&mut self, plan: &plan_node::LogicalValues) -> Cardinality {
183 plan.rows().len().into()
184 }
185
186 fn visit_logical_share(&mut self, plan: &plan_node::LogicalShare) -> Cardinality {
187 self.visit(plan.input())
188 }
189
190 fn visit_logical_dedup(&mut self, plan: &plan_node::LogicalDedup) -> Cardinality {
191 let input = self.visit(plan.input());
192 if plan.dedup_cols().is_empty() {
193 input.min(1)
194 } else {
195 input
196 }
197 }
198
199 fn visit_logical_over_window(&mut self, plan: &super::LogicalOverWindow) -> Self::Result {
200 self.visit(plan.input())
201 }
202
203 fn visit_logical_agg(&mut self, plan: &plan_node::LogicalAgg) -> Cardinality {
204 let input = self.visit(plan.input());
205
206 if plan.group_key().is_empty() {
207 1.into()
209 } else {
210 input.min(1..)
211 }
212 }
213
214 fn visit_logical_limit(&mut self, plan: &plan_node::LogicalLimit) -> Cardinality {
215 self.visit(plan.input()).min(plan.limit() as usize)
216 }
217
218 fn visit_logical_max_one_row(&mut self, plan: &plan_node::LogicalMaxOneRow) -> Cardinality {
219 self.visit(plan.input()).min(1)
220 }
221
222 fn visit_logical_project(&mut self, plan: &plan_node::LogicalProject) -> Cardinality {
223 self.visit(plan.input())
224 }
225
226 fn visit_logical_top_n(&mut self, plan: &plan_node::LogicalTopN) -> Cardinality {
227 let input = self.visit(plan.input());
228
229 let each_group = match plan.limit_attr() {
230 TopNLimit::Simple(limit) => input.sub(plan.offset() as usize).min(limit as usize),
231 TopNLimit::WithTies(limit) => {
232 assert_eq!(plan.offset(), 0, "ties with offset is not supported yet");
233 input.min((limit as usize)..)
234 }
235 };
236
237 if plan.group_key().is_empty() {
238 each_group
239 } else {
240 let group_number = input.min(1..);
241 each_group
242 .mul(group_number)
243 .min(input)
245 }
246 }
247
248 fn visit_logical_filter(&mut self, plan: &plan_node::LogicalFilter) -> Cardinality {
249 let eq_set = Self::fixed_columns(plan.predicate());
250 Self::visit_predicate(&*plan.input(), self.visit(plan.input()), eq_set)
251 }
252
253 fn visit_logical_scan(&mut self, plan: &plan_node::LogicalScan) -> Cardinality {
254 let fixed_table_columns = Self::fixed_columns(plan.predicate());
256 let eq_set = plan
257 .output_col_idx()
258 .iter()
259 .positions(|table_column| fixed_table_columns.contains(table_column))
260 .collect();
261 Self::visit_predicate(plan, plan.table_cardinality(), eq_set)
262 }
263
264 fn visit_logical_union(&mut self, plan: &plan_node::LogicalUnion) -> Cardinality {
265 let all = plan
266 .inputs()
267 .into_iter()
268 .map(|input| self.visit(input))
269 .fold(Cardinality::unknown(), std::ops::Add::add);
270
271 if plan.all() { all } else { all.min(1..) }
272 }
273
274 fn visit_logical_join(&mut self, plan: &plan_node::LogicalJoin) -> Cardinality {
275 let left = self.visit(plan.left());
276 let right = self.visit(plan.right());
277 join_cardinality(plan.join_type(), left, right)
278 }
279
280 fn visit_logical_apply(&mut self, plan: &plan_node::LogicalApply) -> Cardinality {
281 let left = self.visit(plan.left());
283 let right = self.visit(plan.right());
284 join_cardinality(plan.join_type(), left, right)
285 }
286
287 fn visit_logical_now(&mut self, plan: &plan_node::LogicalNow) -> Cardinality {
288 if plan.max_one_row() {
289 1.into()
290 } else {
291 Cardinality::unknown()
292 }
293 }
294
295 fn visit_logical_expand(&mut self, plan: &plan_node::LogicalExpand) -> Cardinality {
296 self.visit(plan.input()) * plan.column_subsets().len()
297 }
298}
299
300fn join_cardinality(join_type: JoinType, left: Cardinality, right: Cardinality) -> Cardinality {
301 match join_type {
302 JoinType::Unspecified => unreachable!(),
303
304 JoinType::Inner => left.mul(right.min(0..)),
306
307 JoinType::LeftOuter => left.mul(right.max(1).min(1..)),
310 JoinType::RightOuter => right.mul(left.max(1).min(1..)),
311
312 JoinType::LeftSemi | JoinType::LeftAnti => left.min(0..),
314 JoinType::RightSemi | JoinType::RightAnti => right.min(0..),
315
316 JoinType::FullOuter => {
318 if left.is_at_most(1) && right.is_at_most(1) {
319 Cardinality::new(0, 1)
320 } else {
321 Cardinality::unknown()
322 }
323 }
324
325 JoinType::AsofInner => left.mul(right.min(0..=1)),
327 JoinType::AsofLeftOuter => left,
329 }
330}
331
332#[easy_ext::ext(LogicalCardinalityExt)]
333pub impl PlanRef {
334 fn max_one_row(&self) -> bool {
336 CardinalityVisitor.visit(self.clone()).is_at_most(1)
337 }
338
339 fn row_count(&self) -> Option<usize> {
341 CardinalityVisitor.visit(self.clone()).get_exact()
342 }
343}