1use itertools::Itertools;
16use risingwave_common::bail;
17use thiserror_ext::AsReport as _;
18
19use super::plan_node::{ConventionMarker, Logical, LogicalPlanRef};
20use super::plan_visitor::has_logical_max_one_row;
21use crate::error::Result;
22use crate::expr::NowProcTimeFinder;
23use crate::optimizer::PlanRef;
24use crate::optimizer::heuristic_optimizer::{ApplyOrder, HeuristicOptimizer};
25use crate::optimizer::plan_node::{
26 ColPrunable, ColumnPruningContext, PredicatePushdown, PredicatePushdownContext,
27 VisitExprsRecursive,
28};
29use crate::optimizer::plan_rewriter::ShareSourceRewriter;
30#[cfg(debug_assertions)]
31use crate::optimizer::plan_visitor::InputRefValidator;
32use crate::optimizer::plan_visitor::{
33 HasMaxOneRowApply, PlanCheckApplyEliminationExt, PlanVisitor, has_logical_apply,
34};
35use crate::optimizer::rule::*;
36use crate::utils::Condition;
37use crate::{Binder, Explain, OptimizerContextRef, Planner};
38
39impl<C: ConventionMarker> PlanRef<C> {
40 fn optimize_by_rules_inner(
41 self,
42 heuristic_optimizer: &mut HeuristicOptimizer<'_, C>,
43 stage_name: &str,
44 ) -> Result<PlanRef<C>> {
45 let ctx = self.ctx();
46
47 let result = heuristic_optimizer.optimize(self);
48 let stats = heuristic_optimizer.get_stats();
49
50 if ctx.is_explain_trace() && stats.has_applied_rule() {
51 ctx.trace(format!("{}:", stage_name));
52 ctx.trace(format!("{}", stats));
53 ctx.trace(match &result {
54 Ok(plan) => plan.explain_to_string(),
55 Err(error) => format!("Optimization failed: {}", error.as_report()),
56 });
57 }
58 ctx.add_rule_applied(stats.total_applied());
59
60 result
61 }
62
63 pub(crate) fn optimize_by_rules(
64 self,
65 OptimizationStage {
66 stage_name,
67 rules,
68 apply_order,
69 }: &OptimizationStage<C>,
70 ) -> Result<PlanRef<C>> {
71 self.optimize_by_rules_inner(&mut HeuristicOptimizer::new(apply_order, rules), stage_name)
72 }
73
74 pub(crate) fn optimize_by_rules_until_fix_point(
75 mut self,
76 OptimizationStage {
77 stage_name,
78 rules,
79 apply_order,
80 }: &OptimizationStage<C>,
81 ) -> Result<PlanRef<C>> {
82 loop {
83 let mut heuristic_optimizer = HeuristicOptimizer::new(apply_order, rules);
84 self = self.optimize_by_rules_inner(&mut heuristic_optimizer, stage_name)?;
85 if !heuristic_optimizer.get_stats().has_applied_rule() {
86 return Ok(self);
87 }
88 }
89 }
90}
91
92pub struct OptimizationStage<C: ConventionMarker = Logical> {
93 stage_name: String,
94 rules: Vec<BoxedRule<C>>,
95 apply_order: ApplyOrder,
96}
97
98impl<C: ConventionMarker> OptimizationStage<C> {
99 pub fn new<S>(name: S, rules: Vec<BoxedRule<C>>, apply_order: ApplyOrder) -> Self
100 where
101 S: Into<String>,
102 {
103 OptimizationStage {
104 stage_name: name.into(),
105 rules,
106 apply_order,
107 }
108 }
109}
110
111use std::collections::{HashMap, HashSet};
112use std::sync::LazyLock;
113
114use risingwave_common::id::ObjectId;
115use risingwave_pb::common::PbObjectType;
116use risingwave_sqlparser::ast::Statement;
117
118use crate::optimizer::plan_node::generic::GenericPlanRef;
119use crate::optimizer::plan_visitor::RelationCollectorVisitor;
120use crate::session::SessionImpl;
121
122pub struct LogicalOptimizer {}
123
124static DAG_TO_TREE: LazyLock<OptimizationStage> = LazyLock::new(|| {
125 OptimizationStage::new(
126 "DAG To Tree",
127 vec![DagToTreeRule::create()],
128 ApplyOrder::TopDown,
129 )
130});
131
132static STREAM_GENERATE_SERIES_WITH_NOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
133 OptimizationStage::new(
134 "Convert GENERATE_SERIES Ends With NOW",
135 vec![GenerateSeriesWithNowRule::create()],
136 ApplyOrder::TopDown,
137 )
138});
139
140static TABLE_FUNCTION_CONVERT: LazyLock<OptimizationStage> = LazyLock::new(|| {
141 OptimizationStage::new(
142 "Table Function Convert",
143 vec![
144 TableFunctionToFileScanRule::create(),
146 TableFunctionToInternalBackfillProgressRule::create(),
148 TableFunctionToInternalSourceBackfillProgressRule::create(),
150 TableFunctionToInternalGetChannelDeltaStatsRule::create(),
152 TableFunctionToPostgresQueryRule::create(),
154 TableFunctionToMySqlQueryRule::create(),
156 TableFunctionToProjectSetRule::create(),
158 ],
159 ApplyOrder::TopDown,
160 )
161});
162
163static TABLE_FUNCTION_TO_FILE_SCAN: LazyLock<OptimizationStage> = LazyLock::new(|| {
164 OptimizationStage::new(
165 "Table Function To FileScan",
166 vec![TableFunctionToFileScanRule::create()],
167 ApplyOrder::TopDown,
168 )
169});
170
171static TABLE_FUNCTION_TO_POSTGRES_QUERY: LazyLock<OptimizationStage> = LazyLock::new(|| {
172 OptimizationStage::new(
173 "Table Function To PostgresQuery",
174 vec![TableFunctionToPostgresQueryRule::create()],
175 ApplyOrder::TopDown,
176 )
177});
178
179static TABLE_FUNCTION_TO_MYSQL_QUERY: LazyLock<OptimizationStage> = LazyLock::new(|| {
180 OptimizationStage::new(
181 "Table Function To MySQL",
182 vec![TableFunctionToMySqlQueryRule::create()],
183 ApplyOrder::TopDown,
184 )
185});
186
187static TABLE_FUNCTION_TO_INTERNAL_BACKFILL_PROGRESS: LazyLock<OptimizationStage> =
188 LazyLock::new(|| {
189 OptimizationStage::new(
190 "Table Function To Internal Backfill Progress",
191 vec![TableFunctionToInternalBackfillProgressRule::create()],
192 ApplyOrder::TopDown,
193 )
194 });
195
196static TABLE_FUNCTION_TO_INTERNAL_SOURCE_BACKFILL_PROGRESS: LazyLock<OptimizationStage> =
197 LazyLock::new(|| {
198 OptimizationStage::new(
199 "Table Function To Internal Source Backfill Progress",
200 vec![TableFunctionToInternalSourceBackfillProgressRule::create()],
201 ApplyOrder::TopDown,
202 )
203 });
204
205static TABLE_FUNCTION_TO_INTERNAL_GET_CHANNEL_DELTA_STATS: LazyLock<OptimizationStage> =
206 LazyLock::new(|| {
207 OptimizationStage::new(
208 "Table Function To Internal Get Channel Delta Stats",
209 vec![TableFunctionToInternalGetChannelDeltaStatsRule::create()],
210 ApplyOrder::TopDown,
211 )
212 });
213
214static VALUES_EXTRACT_PROJECT: LazyLock<OptimizationStage> = LazyLock::new(|| {
215 OptimizationStage::new(
216 "Values Extract Project",
217 vec![ValuesExtractProjectRule::create()],
218 ApplyOrder::TopDown,
219 )
220});
221
222static SIMPLE_UNNESTING: LazyLock<OptimizationStage> = LazyLock::new(|| {
223 OptimizationStage::new(
224 "Simple Unnesting",
225 vec![
226 PullUpCorrelatedPredicateRule::create(),
228 PullUpCorrelatedProjectValueRule::create(),
230 PullUpCorrelatedPredicateAggRule::create(),
231 MaxOneRowEliminateRule::create(),
233 ApplyTableFunctionToProjectSetRule::create(),
235 ApplyToJoinRule::create(),
237 ],
238 ApplyOrder::BottomUp,
239 )
240});
241
242static SET_OPERATION_MERGE: LazyLock<OptimizationStage> = LazyLock::new(|| {
243 OptimizationStage::new(
244 "Set Operation Merge",
245 vec![
246 UnionMergeRule::create(),
247 IntersectMergeRule::create(),
248 ExceptMergeRule::create(),
249 ],
250 ApplyOrder::BottomUp,
251 )
252});
253
254static GENERAL_UNNESTING_TRANS_APPLY_WITH_SHARE: LazyLock<OptimizationStage> =
255 LazyLock::new(|| {
256 OptimizationStage::new(
257 "General Unnesting(Translate Apply)",
258 vec![TranslateApplyRule::create(true)],
259 ApplyOrder::TopDown,
260 )
261 });
262
263static GENERAL_UNNESTING_TRANS_APPLY_WITHOUT_SHARE: LazyLock<OptimizationStage> =
264 LazyLock::new(|| {
265 OptimizationStage::new(
266 "General Unnesting(Translate Apply)",
267 vec![TranslateApplyRule::create(false)],
268 ApplyOrder::TopDown,
269 )
270 });
271
272static GENERAL_UNNESTING_PUSH_DOWN_APPLY: LazyLock<OptimizationStage> = LazyLock::new(|| {
273 OptimizationStage::new(
274 "General Unnesting(Push Down Apply)",
275 vec![
276 ApplyEliminateRule::create(),
277 ApplyAggTransposeRule::create(),
278 ApplyDedupTransposeRule::create(),
279 ApplyFilterTransposeRule::create(),
280 ApplyProjectTransposeRule::create(),
281 ApplyProjectSetTransposeRule::create(),
282 ApplyTopNTransposeRule::create(),
283 ApplyLimitTransposeRule::create(),
284 ApplyJoinTransposeRule::create(),
285 ApplyUnionTransposeRule::create(),
286 ApplyOverWindowTransposeRule::create(),
287 ApplyExpandTransposeRule::create(),
288 ApplyHopWindowTransposeRule::create(),
289 CrossJoinEliminateRule::create(),
290 ApplyShareEliminateRule::create(),
291 ],
292 ApplyOrder::TopDown,
293 )
294});
295
296static TO_MULTI_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
297 OptimizationStage::new(
298 "To MultiJoin",
299 vec![MergeMultiJoinRule::create()],
300 ApplyOrder::TopDown,
301 )
302});
303
304static LEFT_DEEP_JOIN_ORDERING: LazyLock<OptimizationStage> = LazyLock::new(|| {
305 OptimizationStage::new(
306 "Join Ordering".to_owned(),
307 vec![LeftDeepTreeJoinOrderingRule::create()],
308 ApplyOrder::TopDown,
309 )
310});
311
312static BUSHY_TREE_JOIN_ORDERING: LazyLock<OptimizationStage> = LazyLock::new(|| {
313 OptimizationStage::new(
314 "Join Ordering".to_owned(),
315 vec![BushyTreeJoinOrderingRule::create()],
316 ApplyOrder::TopDown,
317 )
318});
319
320static FILTER_WITH_NOW_TO_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
321 OptimizationStage::new(
322 "Push down filter with now into a left semijoin",
323 vec![
324 SplitNowAndRule::create(),
325 SplitNowOrRule::create(),
326 FilterWithNowToJoinRule::create(),
327 ],
328 ApplyOrder::TopDown,
329 )
330});
331
332static PUSH_CALC_OF_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
333 OptimizationStage::new(
334 "Push down the calculation of inputs of join's condition",
335 vec![PushCalculationOfJoinRule::create()],
336 ApplyOrder::TopDown,
337 )
338});
339
340static CONVERT_DISTINCT_AGG_FOR_STREAM: LazyLock<OptimizationStage> = LazyLock::new(|| {
341 OptimizationStage::new(
342 "Convert Distinct Aggregation",
343 vec![UnionToDistinctRule::create(), DistinctAggRule::create(true)],
344 ApplyOrder::TopDown,
345 )
346});
347
348static CONVERT_DISTINCT_AGG_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
349 OptimizationStage::new(
350 "Convert Distinct Aggregation",
351 vec![
352 UnionToDistinctRule::create(),
353 DistinctAggRule::create(false),
354 ],
355 ApplyOrder::TopDown,
356 )
357});
358
359static SIMPLIFY_AGG: LazyLock<OptimizationStage> = LazyLock::new(|| {
360 OptimizationStage::new(
361 "Simplify Aggregation",
362 vec![
363 AggGroupBySimplifyRule::create(),
364 AggCallMergeRule::create(),
365 UnifyFirstLastValueRule::create(),
366 ],
367 ApplyOrder::TopDown,
368 )
369});
370
371static JOIN_COMMUTE: LazyLock<OptimizationStage> = LazyLock::new(|| {
372 OptimizationStage::new(
373 "Join Commute".to_owned(),
374 vec![JoinCommuteRule::create()],
375 ApplyOrder::TopDown,
376 )
377});
378
379static CONSTANT_OUTPUT_REMOVE: LazyLock<OptimizationStage> = LazyLock::new(|| {
380 OptimizationStage::new(
381 "Constant Output Operator Remove",
382 vec![EmptyAggRemoveRule::create()],
383 ApplyOrder::TopDown,
384 )
385});
386
387static PROJECT_REMOVE: LazyLock<OptimizationStage> = LazyLock::new(|| {
388 OptimizationStage::new(
389 "Project Remove",
390 vec![
391 ProjectMergeRule::create(),
393 ProjectEliminateRule::create(),
394 TrivialProjectToValuesRule::create(),
395 UnionInputValuesMergeRule::create(),
396 JoinProjectTransposeRule::create(),
397 ProjectJoinMergeRule::create(),
400 AggProjectMergeRule::create(),
401 ],
402 ApplyOrder::BottomUp,
403 )
404});
405
406static SPLIT_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
407 OptimizationStage::new(
408 "Split Over Window",
409 vec![OverWindowSplitRule::create()],
410 ApplyOrder::TopDown,
411 )
412});
413
414static CONVERT_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
418 OptimizationStage::new(
419 "Convert Over Window",
420 vec![
421 ProjectMergeRule::create(),
422 ProjectEliminateRule::create(),
423 TrivialProjectToValuesRule::create(),
424 UnionInputValuesMergeRule::create(),
425 OverWindowToAggAndJoinRule::create(),
426 OverWindowToTopNRule::create(),
427 ],
428 ApplyOrder::TopDown,
429 )
430});
431
432static CONVERT_OVER_WINDOW_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
434 OptimizationStage::new(
435 "Convert Over Window",
436 vec![
437 ProjectMergeRule::create(),
438 ProjectEliminateRule::create(),
439 TrivialProjectToValuesRule::create(),
440 UnionInputValuesMergeRule::create(),
441 OverWindowToAggAndJoinRule::create(),
442 ],
443 ApplyOrder::TopDown,
444 )
445});
446
447static MERGE_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
448 OptimizationStage::new(
449 "Merge Over Window",
450 vec![OverWindowMergeRule::create()],
451 ApplyOrder::TopDown,
452 )
453});
454
455static REWRITE_LIKE_EXPR: LazyLock<OptimizationStage> = LazyLock::new(|| {
456 OptimizationStage::new(
457 "Rewrite Like Expr",
458 vec![RewriteLikeExprRule::create()],
459 ApplyOrder::TopDown,
460 )
461});
462
463static TOP_N_AGG_ON_INDEX: LazyLock<OptimizationStage> = LazyLock::new(|| {
464 OptimizationStage::new(
465 "TopN/SimpleAgg on Index",
466 vec![
467 TopNProjectTransposeRule::create(),
468 TopNOnIndexRule::create(),
469 MinMaxOnIndexRule::create(),
470 ],
471 ApplyOrder::TopDown,
472 )
473});
474
475static PROJECT_TOP_N_TRANSPOSE: LazyLock<OptimizationStage> = LazyLock::new(|| {
476 OptimizationStage::new(
477 "Project TopN Transpose",
478 vec![ProjectTopNTransposeRule::create()],
479 ApplyOrder::TopDown,
480 )
481});
482
483static ALWAYS_FALSE_FILTER: LazyLock<OptimizationStage> = LazyLock::new(|| {
484 OptimizationStage::new(
485 "Void always-false filter's downstream",
486 vec![AlwaysFalseFilterRule::create()],
487 ApplyOrder::TopDown,
488 )
489});
490
491static LIMIT_PUSH_DOWN: LazyLock<OptimizationStage> = LazyLock::new(|| {
492 OptimizationStage::new(
493 "Push Down Limit",
494 vec![LimitPushDownRule::create()],
495 ApplyOrder::TopDown,
496 )
497});
498
499static PULL_UP_HOP: LazyLock<OptimizationStage> = LazyLock::new(|| {
500 OptimizationStage::new(
501 "Pull Up Hop",
502 vec![PullUpHopRule::create()],
503 ApplyOrder::BottomUp,
504 )
505});
506
507static SET_OPERATION_TO_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
508 OptimizationStage::new(
509 "Set Operation To Join",
510 vec![
511 IntersectToSemiJoinRule::create(),
512 ExceptToAntiJoinRule::create(),
513 ],
514 ApplyOrder::BottomUp,
515 )
516});
517
518static GROUPING_SETS: LazyLock<OptimizationStage> = LazyLock::new(|| {
519 OptimizationStage::new(
520 "Grouping Sets",
521 vec![
522 GroupingSetsToExpandRule::create(),
523 ExpandToProjectRule::create(),
524 ],
525 ApplyOrder::TopDown,
526 )
527});
528
529static COMMON_SUB_EXPR_EXTRACT: LazyLock<OptimizationStage> = LazyLock::new(|| {
530 OptimizationStage::new(
531 "Common Sub Expression Extract",
532 vec![CommonSubExprExtractRule::create()],
533 ApplyOrder::TopDown,
534 )
535});
536
537static LOGICAL_FILTER_EXPRESSION_SIMPLIFY: LazyLock<OptimizationStage> = LazyLock::new(|| {
538 OptimizationStage::new(
539 "Logical Filter Expression Simplify",
540 vec![LogicalFilterExpressionSimplifyRule::create()],
541 ApplyOrder::TopDown,
542 )
543});
544
545static REWRITE_SOURCE_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
546 OptimizationStage::new(
547 "Rewrite Source For Batch",
548 vec![SourceToKafkaScanRule::create()],
549 ApplyOrder::TopDown,
550 )
551});
552
553static MATERIALIZE_ICEBERG_SCAN: LazyLock<OptimizationStage> = LazyLock::new(|| {
554 OptimizationStage::new(
555 "Materialize Iceberg Scan",
556 vec![
557 IcebergEngineStorageSelectionRule::create(),
559 IcebergIntermediateScanRule::create(),
562 ],
563 ApplyOrder::TopDown,
564 )
565});
566
567static ICEBERG_COUNT_STAR: LazyLock<OptimizationStage> = LazyLock::new(|| {
568 OptimizationStage::new(
569 "Iceberg Count Star Optimization",
570 vec![IcebergCountStarRule::create()],
571 ApplyOrder::BottomUp,
572 )
573});
574
575static TOP_N_TO_VECTOR_SEARCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
576 OptimizationStage::new(
577 "TopN to Vector Search",
578 vec![TopNToVectorSearchRule::create()],
579 ApplyOrder::BottomUp,
580 )
581});
582
583static CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_BATCH: LazyLock<OptimizationStage> =
584 LazyLock::new(|| {
585 OptimizationStage::new(
586 "Correlated TopN to Vector Search",
587 vec![CorrelatedTopNToVectorSearchRule::create(true)],
588 ApplyOrder::BottomUp,
589 )
590 });
591
592static CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_STREAM: LazyLock<OptimizationStage> =
593 LazyLock::new(|| {
594 OptimizationStage::new(
595 "Correlated TopN to Vector Search",
596 vec![CorrelatedTopNToVectorSearchRule::create(false)],
597 ApplyOrder::BottomUp,
598 )
599 });
600
601static BATCH_MV_SELECTION: LazyLock<OptimizationStage> = LazyLock::new(|| {
602 OptimizationStage::new(
603 "Batch Mv Selection",
604 vec![MvSelectionRule::create()],
605 ApplyOrder::TopDown,
606 )
607});
608
609impl LogicalOptimizer {
610 pub fn predicate_pushdown(
611 plan: LogicalPlanRef,
612 explain_trace: bool,
613 ctx: &OptimizerContextRef,
614 ) -> LogicalPlanRef {
615 let mut pushdown_ctx = PredicatePushdownContext::new(plan.clone());
618 let plan = plan.predicate_pushdown(Condition::true_cond(), &mut pushdown_ctx);
619 if explain_trace {
620 ctx.trace("Predicate Push Down:");
621 ctx.trace(plan.explain_to_string());
622 }
623 plan
624 }
625
626 pub fn subquery_unnesting(
627 mut plan: LogicalPlanRef,
628 enable_share_plan: bool,
629 explain_trace: bool,
630 ctx: &OptimizerContextRef,
631 ) -> Result<LogicalPlanRef> {
632 if !has_logical_apply(plan.clone()) {
634 return Ok(plan);
635 }
636 plan = plan.optimize_by_rules(&SIMPLE_UNNESTING)?;
638 debug_assert!(!HasMaxOneRowApply().visit(plan.clone()));
639 plan = Self::predicate_pushdown(plan, explain_trace, ctx);
642 plan = plan.optimize_by_rules(&VALUES_EXTRACT_PROJECT)?;
644 plan = if enable_share_plan {
648 plan.optimize_by_rules(&GENERAL_UNNESTING_TRANS_APPLY_WITH_SHARE)?
649 } else {
650 plan.optimize_by_rules(&GENERAL_UNNESTING_TRANS_APPLY_WITHOUT_SHARE)?
651 };
652 plan = plan.optimize_by_rules_until_fix_point(&GENERAL_UNNESTING_PUSH_DOWN_APPLY)?;
653
654 plan.check_apply_elimination()?;
656
657 Ok(plan)
658 }
659
660 pub fn column_pruning(
661 mut plan: LogicalPlanRef,
662 explain_trace: bool,
663 ctx: &OptimizerContextRef,
664 ) -> LogicalPlanRef {
665 let required_cols = (0..plan.schema().len()).collect_vec();
666 let mut column_pruning_ctx = ColumnPruningContext::new(plan.clone());
669 plan = plan.prune_col(&required_cols, &mut column_pruning_ctx);
670 if explain_trace {
672 ctx.trace("Prune Columns:");
673 ctx.trace(plan.explain_to_string());
674 }
675 plan
676 }
677
678 pub fn inline_now_proc_time(plan: LogicalPlanRef, ctx: &OptimizerContextRef) -> LogicalPlanRef {
679 let mut v = NowProcTimeFinder::default();
681 plan.visit_exprs_recursive(&mut v);
682 if !v.has() {
683 return plan;
684 }
685
686 let mut v = ctx.session_ctx().pinned_snapshot().inline_now_proc_time();
687
688 let plan = plan.rewrite_exprs_recursive(&mut v);
689
690 if ctx.is_explain_trace() {
691 ctx.trace("Inline Now and ProcTime:");
692 ctx.trace(plan.explain_to_string());
693 }
694 plan
695 }
696
697 pub fn gen_optimized_logical_plan_for_stream(
698 mut plan: LogicalPlanRef,
699 ) -> Result<LogicalPlanRef> {
700 let ctx = plan.ctx();
701 let explain_trace = ctx.is_explain_trace();
702
703 if explain_trace {
704 ctx.trace("Begin:");
705 ctx.trace(plan.explain_to_string());
706 }
707
708 plan = plan.optimize_by_rules(&GROUPING_SETS)?;
710 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
712 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
714
715 let enable_share_plan = ctx.session_ctx().config().enable_share_plan();
719 if enable_share_plan {
720 plan = plan.common_subplan_sharing();
722 plan = plan.prune_share();
723 if explain_trace {
724 ctx.trace("Common Sub-plan Sharing:");
725 ctx.trace(plan.explain_to_string());
726 }
727 } else {
728 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
729
730 plan = ShareSourceRewriter::share_source(plan);
734 if explain_trace {
735 ctx.trace("Share Source:");
736 ctx.trace(plan.explain_to_string());
737 }
738 }
739 plan = plan.optimize_by_rules(&SET_OPERATION_MERGE)?;
740 plan = plan.optimize_by_rules(&SET_OPERATION_TO_JOIN)?;
741 plan = plan.optimize_by_rules(&STREAM_GENERATE_SERIES_WITH_NOW)?;
744 plan = plan.optimize_by_rules(&TABLE_FUNCTION_CONVERT)?;
746
747 plan = plan.optimize_by_rules(&CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_STREAM)?;
748
749 plan = Self::subquery_unnesting(plan, enable_share_plan, explain_trace, &ctx)?;
750 if has_logical_max_one_row(plan.clone()) {
751 bail!("Scalar subquery might produce more than one row.");
755 }
756
757 plan = plan.optimize_by_rules(&LOGICAL_FILTER_EXPRESSION_SIMPLIFY)?;
760
761 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
763
764 if plan.ctx().session_ctx().config().enable_join_ordering() {
765 plan = plan.optimize_by_rules(&TO_MULTI_JOIN)?;
769
770 if plan
772 .ctx()
773 .session_ctx()
774 .config()
775 .streaming_enable_bushy_join()
776 {
777 plan = plan.optimize_by_rules(&BUSHY_TREE_JOIN_ORDERING)?;
778 } else {
779 plan = plan.optimize_by_rules(&LEFT_DEEP_JOIN_ORDERING)?;
780 }
781 }
782
783 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
786
787 plan = plan.optimize_by_rules(&FILTER_WITH_NOW_TO_JOIN)?;
789
790 plan = plan.optimize_by_rules(&PUSH_CALC_OF_JOIN)?;
792
793 plan = plan.optimize_by_rules(&SPLIT_OVER_WINDOW)?;
794 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
797 plan = plan.optimize_by_rules(&CONVERT_OVER_WINDOW)?;
798 plan = plan.optimize_by_rules(&MERGE_OVER_WINDOW)?;
799
800 let force_split_distinct_agg = ctx.session_ctx().config().force_split_distinct_agg();
801 plan = if force_split_distinct_agg {
804 plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_BATCH)?
805 } else {
806 plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_STREAM)?
807 };
808
809 plan = plan.optimize_by_rules(&SIMPLIFY_AGG)?;
810
811 plan = plan.optimize_by_rules(&JOIN_COMMUTE)?;
812
813 plan = Self::column_pruning(plan, explain_trace, &ctx);
815 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
816
817 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
818 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
819
820 plan = plan.optimize_by_rules(&COMMON_SUB_EXPR_EXTRACT)?;
821
822 #[cfg(debug_assertions)]
823 InputRefValidator.validate(plan.clone());
824
825 ctx.may_store_explain_logical(&plan);
826
827 Ok(plan)
828 }
829
830 pub fn gen_optimized_logical_plan_for_batch(
831 mut plan: LogicalPlanRef,
832 ) -> Result<LogicalPlanRef> {
833 let ctx = plan.ctx();
834 let explain_trace = ctx.is_explain_trace();
835
836 if explain_trace {
837 ctx.trace("Begin:");
838 ctx.trace(plan.explain_to_string());
839 }
840
841 if ctx.session_ctx().config().enable_mv_selection() {
842 let query_relations =
843 RelationCollectorVisitor::collect_with(HashSet::new(), plan.clone());
844 Self::register_batch_mview_candidates(ctx.session_ctx(), &ctx, &query_relations);
845 plan = plan.optimize_by_rules(&BATCH_MV_SELECTION)?;
846 }
847
848 plan = Self::inline_now_proc_time(plan, &ctx);
850
851 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
853
854 plan = plan.optimize_by_rules(&REWRITE_SOURCE_FOR_BATCH)?;
855 plan = plan.optimize_by_rules(&GROUPING_SETS)?;
856 plan = plan.optimize_by_rules(&REWRITE_LIKE_EXPR)?;
857 plan = plan.optimize_by_rules(&SET_OPERATION_MERGE)?;
858 plan = plan.optimize_by_rules(&SET_OPERATION_TO_JOIN)?;
859 plan = plan.optimize_by_rules(&ALWAYS_FALSE_FILTER)?;
860 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_FILE_SCAN)?;
862 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_POSTGRES_QUERY)?;
863 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_MYSQL_QUERY)?;
864 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_BACKFILL_PROGRESS)?;
865 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_GET_CHANNEL_DELTA_STATS)?;
866 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_SOURCE_BACKFILL_PROGRESS)?;
867 plan = plan.optimize_by_rules(&TABLE_FUNCTION_CONVERT)?;
869
870 plan = plan.optimize_by_rules(&CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_BATCH)?;
871
872 plan = Self::subquery_unnesting(plan, false, explain_trace, &ctx)?;
873
874 plan = plan.optimize_by_rules(&LOGICAL_FILTER_EXPRESSION_SIMPLIFY)?;
878
879 let mut last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
881 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
882
883 if plan.ctx().session_ctx().config().enable_join_ordering() {
884 plan = plan.optimize_by_rules(&TO_MULTI_JOIN)?;
888
889 plan = plan.optimize_by_rules(&LEFT_DEEP_JOIN_ORDERING)?;
891 }
892
893 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
896 last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
897 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
898 }
899
900 plan = plan.optimize_by_rules(&PUSH_CALC_OF_JOIN)?;
902
903 plan = plan.optimize_by_rules(&SPLIT_OVER_WINDOW)?;
904 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
907 last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
908 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
909 }
910 plan = plan.optimize_by_rules(&CONVERT_OVER_WINDOW_FOR_BATCH)?;
911 plan = plan.optimize_by_rules(&MERGE_OVER_WINDOW)?;
912
913 plan = plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_BATCH)?;
915
916 plan = plan.optimize_by_rules(&SIMPLIFY_AGG)?;
917
918 plan = plan.optimize_by_rules(&JOIN_COMMUTE)?;
919
920 plan = plan.optimize_by_rules(&TOP_N_TO_VECTOR_SEARCH)?;
921
922 plan = Self::column_pruning(plan, explain_trace, &ctx);
924 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
925 let _ = ctx.total_rule_applied();
926 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
927 }
928
929 plan = plan.optimize_by_rules(&MATERIALIZE_ICEBERG_SCAN)?;
931
932 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
933 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
934
935 plan = plan.optimize_by_rules(&COMMON_SUB_EXPR_EXTRACT)?;
936
937 plan = plan.optimize_by_rules(&ICEBERG_COUNT_STAR)?;
939
940 plan = plan.optimize_by_rules(&PULL_UP_HOP)?;
941
942 plan = plan.optimize_by_rules(&TOP_N_AGG_ON_INDEX)?;
943
944 plan = plan.optimize_by_rules(&PROJECT_TOP_N_TRANSPOSE)?;
945
946 plan = plan.optimize_by_rules(&LIMIT_PUSH_DOWN)?;
947
948 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
949
950 #[cfg(debug_assertions)]
951 InputRefValidator.validate(plan.clone());
952
953 ctx.may_store_explain_logical(&plan);
954
955 Ok(plan)
956 }
957
958 fn register_batch_mview_candidates(
959 session: &SessionImpl,
960 context: &OptimizerContextRef,
961 query_relations: &HashSet<ObjectId>,
962 ) {
963 let catalog_reader = session.env().catalog_reader().read_guard();
964 let user_reader = session.env().user_info_reader().read_guard();
965 let Some(current_user) = user_reader.get_user_by_name(&session.user_name()) else {
966 return;
967 };
968 let mut mv_dependencies: HashMap<ObjectId, HashSet<ObjectId>> = HashMap::new();
969 let mut mviews_with_source_dependency: HashSet<ObjectId> = HashSet::new();
970 for dep in catalog_reader.iter_object_dependencies() {
971 mv_dependencies
972 .entry(dep.object_id)
973 .or_default()
974 .insert(dep.referenced_object_id);
975 if dep.referenced_object_type == PbObjectType::Source {
976 mviews_with_source_dependency.insert(dep.object_id);
977 }
978 }
979 let db_name = session.database();
980 let Ok(schemas) = catalog_reader.iter_schemas(&db_name) else {
981 return;
982 };
983
984 for schema in schemas {
985 for mv in schema.iter_created_mvs_with_acl(current_user) {
986 let mv_object_id = mv.id().as_object_id();
987 if mviews_with_source_dependency.contains(&mv_object_id) {
988 continue;
989 }
990 let is_subset = mv_dependencies
991 .get(&mv_object_id)
992 .is_some_and(|deps| deps.is_subset(query_relations));
993 if !is_subset {
994 continue;
995 }
996 let Ok(stmt) = mv.create_sql_ast() else {
997 continue;
998 };
999 let Statement::CreateView {
1000 materialized: true,
1001 query,
1002 ..
1003 } = stmt
1004 else {
1005 continue;
1006 };
1007 let mut binder = Binder::new_for_batch(session);
1008 let Ok(bound_query) = binder.bind_query(&query) else {
1009 continue;
1010 };
1011 let mut planner = Planner::new_for_batch_dql(context.clone());
1012 let Ok(plan_root) = planner.plan_query(bound_query) else {
1013 continue;
1014 };
1015 context.add_batch_mview_candidate(mv.clone(), plan_root.plan.clone());
1016 }
1017 }
1018 }
1019}