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 TableFunctionToMssqlQueryRule::create(),
158 TableFunctionToProjectSetRule::create(),
160 ],
161 ApplyOrder::TopDown,
162 )
163});
164
165static TABLE_FUNCTION_TO_FILE_SCAN: LazyLock<OptimizationStage> = LazyLock::new(|| {
166 OptimizationStage::new(
167 "Table Function To FileScan",
168 vec![TableFunctionToFileScanRule::create()],
169 ApplyOrder::TopDown,
170 )
171});
172
173static TABLE_FUNCTION_TO_POSTGRES_QUERY: LazyLock<OptimizationStage> = LazyLock::new(|| {
174 OptimizationStage::new(
175 "Table Function To PostgresQuery",
176 vec![TableFunctionToPostgresQueryRule::create()],
177 ApplyOrder::TopDown,
178 )
179});
180
181static TABLE_FUNCTION_TO_MYSQL_QUERY: LazyLock<OptimizationStage> = LazyLock::new(|| {
182 OptimizationStage::new(
183 "Table Function To MySQL",
184 vec![TableFunctionToMySqlQueryRule::create()],
185 ApplyOrder::TopDown,
186 )
187});
188
189static TABLE_FUNCTION_TO_MSSQL_QUERY: LazyLock<OptimizationStage> = LazyLock::new(|| {
190 OptimizationStage::new(
191 "Table Function To MSSQL Query",
192 vec![TableFunctionToMssqlQueryRule::create()],
193 ApplyOrder::TopDown,
197 )
198});
199
200static TABLE_FUNCTION_TO_INTERNAL_BACKFILL_PROGRESS: LazyLock<OptimizationStage> =
201 LazyLock::new(|| {
202 OptimizationStage::new(
203 "Table Function To Internal Backfill Progress",
204 vec![TableFunctionToInternalBackfillProgressRule::create()],
205 ApplyOrder::TopDown,
206 )
207 });
208
209static TABLE_FUNCTION_TO_INTERNAL_SOURCE_BACKFILL_PROGRESS: LazyLock<OptimizationStage> =
210 LazyLock::new(|| {
211 OptimizationStage::new(
212 "Table Function To Internal Source Backfill Progress",
213 vec![TableFunctionToInternalSourceBackfillProgressRule::create()],
214 ApplyOrder::TopDown,
215 )
216 });
217
218static TABLE_FUNCTION_TO_INTERNAL_GET_CHANNEL_DELTA_STATS: LazyLock<OptimizationStage> =
219 LazyLock::new(|| {
220 OptimizationStage::new(
221 "Table Function To Internal Get Channel Delta Stats",
222 vec![TableFunctionToInternalGetChannelDeltaStatsRule::create()],
223 ApplyOrder::TopDown,
224 )
225 });
226
227static VALUES_EXTRACT_PROJECT: LazyLock<OptimizationStage> = LazyLock::new(|| {
228 OptimizationStage::new(
229 "Values Extract Project",
230 vec![ValuesExtractProjectRule::create()],
231 ApplyOrder::TopDown,
232 )
233});
234
235static SIMPLE_UNNESTING: LazyLock<OptimizationStage> = LazyLock::new(|| {
236 OptimizationStage::new(
237 "Simple Unnesting",
238 vec![
239 PullUpCorrelatedPredicateRule::create(),
241 PullUpCorrelatedProjectValueRule::create(),
243 PullUpCorrelatedPredicateAggRule::create(),
244 MaxOneRowEliminateRule::create(),
246 ApplyTableFunctionToProjectSetRule::create(),
248 ApplyToJoinRule::create(),
250 ],
251 ApplyOrder::BottomUp,
252 )
253});
254
255static SET_OPERATION_MERGE: LazyLock<OptimizationStage> = LazyLock::new(|| {
256 OptimizationStage::new(
257 "Set Operation Merge",
258 vec![
259 UnionMergeRule::create(),
260 IntersectMergeRule::create(),
261 ExceptMergeRule::create(),
262 ],
263 ApplyOrder::BottomUp,
264 )
265});
266
267static GENERAL_UNNESTING_TRANS_APPLY_WITH_SHARE: LazyLock<OptimizationStage> =
268 LazyLock::new(|| {
269 OptimizationStage::new(
270 "General Unnesting(Translate Apply)",
271 vec![TranslateApplyRule::create(true)],
272 ApplyOrder::TopDown,
273 )
274 });
275
276static GENERAL_UNNESTING_TRANS_APPLY_WITHOUT_SHARE: LazyLock<OptimizationStage> =
277 LazyLock::new(|| {
278 OptimizationStage::new(
279 "General Unnesting(Translate Apply)",
280 vec![TranslateApplyRule::create(false)],
281 ApplyOrder::TopDown,
282 )
283 });
284
285static GENERAL_UNNESTING_PUSH_DOWN_APPLY: LazyLock<OptimizationStage> = LazyLock::new(|| {
286 OptimizationStage::new(
287 "General Unnesting(Push Down Apply)",
288 vec![
289 ApplyEliminateRule::create(),
290 ApplyAggTransposeRule::create(),
291 ApplyDedupTransposeRule::create(),
292 ApplyFilterTransposeRule::create(),
293 ApplyProjectTransposeRule::create(),
294 ApplyProjectSetTransposeRule::create(),
295 ApplyTopNTransposeRule::create(),
296 ApplyLimitTransposeRule::create(),
297 ApplyJoinTransposeRule::create(),
298 ApplyUnionTransposeRule::create(),
299 ApplyOverWindowTransposeRule::create(),
300 ApplyExpandTransposeRule::create(),
301 ApplyHopWindowTransposeRule::create(),
302 CrossJoinEliminateRule::create(),
303 ApplyShareEliminateRule::create(),
304 ],
305 ApplyOrder::TopDown,
306 )
307});
308
309static TO_MULTI_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
310 OptimizationStage::new(
311 "To MultiJoin",
312 vec![MergeMultiJoinRule::create()],
313 ApplyOrder::TopDown,
314 )
315});
316
317static LEFT_DEEP_JOIN_ORDERING: LazyLock<OptimizationStage> = LazyLock::new(|| {
318 OptimizationStage::new(
319 "Join Ordering".to_owned(),
320 vec![LeftDeepTreeJoinOrderingRule::create()],
321 ApplyOrder::TopDown,
322 )
323});
324
325static BUSHY_TREE_JOIN_ORDERING: LazyLock<OptimizationStage> = LazyLock::new(|| {
326 OptimizationStage::new(
327 "Join Ordering".to_owned(),
328 vec![BushyTreeJoinOrderingRule::create()],
329 ApplyOrder::TopDown,
330 )
331});
332
333static FILTER_WITH_NOW_TO_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
334 OptimizationStage::new(
335 "Push down filter with now into a left semijoin",
336 vec![
337 SplitNowAndRule::create(),
338 SplitNowOrRule::create(),
339 FilterWithNowToJoinRule::create(),
340 ],
341 ApplyOrder::TopDown,
342 )
343});
344
345static PUSH_CALC_OF_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
346 OptimizationStage::new(
347 "Push down the calculation of inputs of join's condition",
348 vec![PushCalculationOfJoinRule::create()],
349 ApplyOrder::TopDown,
350 )
351});
352
353static CONVERT_DISTINCT_AGG_FOR_STREAM: LazyLock<OptimizationStage> = LazyLock::new(|| {
354 OptimizationStage::new(
355 "Convert Distinct Aggregation",
356 vec![UnionToDistinctRule::create(), DistinctAggRule::create(true)],
357 ApplyOrder::TopDown,
358 )
359});
360
361static CONVERT_DISTINCT_AGG_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
362 OptimizationStage::new(
363 "Convert Distinct Aggregation",
364 vec![
365 UnionToDistinctRule::create(),
366 DistinctAggRule::create(false),
367 ],
368 ApplyOrder::TopDown,
369 )
370});
371
372static SIMPLIFY_AGG: LazyLock<OptimizationStage> = LazyLock::new(|| {
373 OptimizationStage::new(
374 "Simplify Aggregation",
375 vec![
376 AggGroupBySimplifyRule::create(),
377 AggCallMergeRule::create(),
378 UnifyFirstLastValueRule::create(),
379 ],
380 ApplyOrder::TopDown,
381 )
382});
383
384static JOIN_COMMUTE: LazyLock<OptimizationStage> = LazyLock::new(|| {
385 OptimizationStage::new(
386 "Join Commute".to_owned(),
387 vec![JoinCommuteRule::create()],
388 ApplyOrder::TopDown,
389 )
390});
391
392static CONSTANT_OUTPUT_REMOVE: LazyLock<OptimizationStage> = LazyLock::new(|| {
393 OptimizationStage::new(
394 "Constant Output Operator Remove",
395 vec![EmptyAggRemoveRule::create()],
396 ApplyOrder::TopDown,
397 )
398});
399
400static PROJECT_REMOVE: LazyLock<OptimizationStage> = LazyLock::new(|| {
401 OptimizationStage::new(
402 "Project Remove",
403 vec![
404 ProjectMergeRule::create(),
406 ProjectEliminateRule::create(),
407 TrivialProjectToValuesRule::create(),
408 UnionInputValuesMergeRule::create(),
409 JoinProjectTransposeRule::create(),
410 ProjectJoinMergeRule::create(),
413 AggProjectMergeRule::create(),
414 ],
415 ApplyOrder::BottomUp,
416 )
417});
418
419static SPLIT_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
420 OptimizationStage::new(
421 "Split Over Window",
422 vec![OverWindowSplitRule::create()],
423 ApplyOrder::TopDown,
424 )
425});
426
427static CONVERT_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
431 OptimizationStage::new(
432 "Convert Over Window",
433 vec![
434 ProjectMergeRule::create(),
435 ProjectEliminateRule::create(),
436 TrivialProjectToValuesRule::create(),
437 UnionInputValuesMergeRule::create(),
438 OverWindowToAggAndJoinRule::create(),
439 OverWindowToTopNRule::create(),
440 ],
441 ApplyOrder::TopDown,
442 )
443});
444
445static CONVERT_OVER_WINDOW_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
447 OptimizationStage::new(
448 "Convert Over Window",
449 vec![
450 ProjectMergeRule::create(),
451 ProjectEliminateRule::create(),
452 TrivialProjectToValuesRule::create(),
453 UnionInputValuesMergeRule::create(),
454 OverWindowToAggAndJoinRule::create(),
455 ],
456 ApplyOrder::TopDown,
457 )
458});
459
460static MERGE_OVER_WINDOW: LazyLock<OptimizationStage> = LazyLock::new(|| {
461 OptimizationStage::new(
462 "Merge Over Window",
463 vec![OverWindowMergeRule::create()],
464 ApplyOrder::TopDown,
465 )
466});
467
468static REWRITE_LIKE_EXPR: LazyLock<OptimizationStage> = LazyLock::new(|| {
469 OptimizationStage::new(
470 "Rewrite Like Expr",
471 vec![RewriteLikeExprRule::create()],
472 ApplyOrder::TopDown,
473 )
474});
475
476static TOP_N_AGG_ON_INDEX: LazyLock<OptimizationStage> = LazyLock::new(|| {
477 OptimizationStage::new(
478 "TopN/SimpleAgg on Index",
479 vec![
480 TopNProjectTransposeRule::create(),
481 TopNOnIndexRule::create(),
482 MinMaxOnIndexRule::create(),
483 ],
484 ApplyOrder::TopDown,
485 )
486});
487
488static PROJECT_TOP_N_TRANSPOSE: LazyLock<OptimizationStage> = LazyLock::new(|| {
489 OptimizationStage::new(
490 "Project TopN Transpose",
491 vec![ProjectTopNTransposeRule::create()],
492 ApplyOrder::TopDown,
493 )
494});
495
496static ALWAYS_FALSE_FILTER: LazyLock<OptimizationStage> = LazyLock::new(|| {
497 OptimizationStage::new(
498 "Void always-false filter's downstream",
499 vec![AlwaysFalseFilterRule::create()],
500 ApplyOrder::TopDown,
501 )
502});
503
504static LIMIT_PUSH_DOWN: LazyLock<OptimizationStage> = LazyLock::new(|| {
505 OptimizationStage::new(
506 "Push Down Limit",
507 vec![LimitPushDownRule::create()],
508 ApplyOrder::TopDown,
509 )
510});
511
512static PULL_UP_HOP: LazyLock<OptimizationStage> = LazyLock::new(|| {
513 OptimizationStage::new(
514 "Pull Up Hop",
515 vec![PullUpHopRule::create()],
516 ApplyOrder::BottomUp,
517 )
518});
519
520static SET_OPERATION_TO_JOIN: LazyLock<OptimizationStage> = LazyLock::new(|| {
521 OptimizationStage::new(
522 "Set Operation To Join",
523 vec![
524 IntersectToSemiJoinRule::create(),
525 ExceptToAntiJoinRule::create(),
526 ],
527 ApplyOrder::BottomUp,
528 )
529});
530
531static GROUPING_SETS: LazyLock<OptimizationStage> = LazyLock::new(|| {
532 OptimizationStage::new(
533 "Grouping Sets",
534 vec![
535 GroupingSetsToExpandRule::create(),
536 ExpandToProjectRule::create(),
537 ],
538 ApplyOrder::TopDown,
539 )
540});
541
542static COMMON_SUB_EXPR_EXTRACT: LazyLock<OptimizationStage> = LazyLock::new(|| {
543 OptimizationStage::new(
544 "Common Sub Expression Extract",
545 vec![CommonSubExprExtractRule::create()],
546 ApplyOrder::TopDown,
547 )
548});
549
550static LOGICAL_FILTER_EXPRESSION_SIMPLIFY: LazyLock<OptimizationStage> = LazyLock::new(|| {
551 OptimizationStage::new(
552 "Logical Filter Expression Simplify",
553 vec![LogicalFilterExpressionSimplifyRule::create()],
554 ApplyOrder::TopDown,
555 )
556});
557
558static REWRITE_SOURCE_FOR_BATCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
559 OptimizationStage::new(
560 "Rewrite Source For Batch",
561 vec![SourceToKafkaScanRule::create()],
562 ApplyOrder::TopDown,
563 )
564});
565
566static MATERIALIZE_ICEBERG_SCAN: LazyLock<OptimizationStage> = LazyLock::new(|| {
567 OptimizationStage::new(
568 "Materialize Iceberg Scan",
569 vec![
570 IcebergEngineStorageSelectionRule::create(),
572 IcebergIntermediateScanRule::create(),
575 ],
576 ApplyOrder::TopDown,
577 )
578});
579
580static ICEBERG_COUNT_STAR: LazyLock<OptimizationStage> = LazyLock::new(|| {
581 OptimizationStage::new(
582 "Iceberg Count Star Optimization",
583 vec![IcebergCountStarRule::create()],
584 ApplyOrder::BottomUp,
585 )
586});
587
588static TOP_N_TO_VECTOR_SEARCH: LazyLock<OptimizationStage> = LazyLock::new(|| {
589 OptimizationStage::new(
590 "TopN to Vector Search",
591 vec![TopNToVectorSearchRule::create()],
592 ApplyOrder::BottomUp,
593 )
594});
595
596static CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_BATCH: LazyLock<OptimizationStage> =
597 LazyLock::new(|| {
598 OptimizationStage::new(
599 "Correlated TopN to Vector Search",
600 vec![CorrelatedTopNToVectorSearchRule::create(true)],
601 ApplyOrder::BottomUp,
602 )
603 });
604
605static CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_STREAM: LazyLock<OptimizationStage> =
606 LazyLock::new(|| {
607 OptimizationStage::new(
608 "Correlated TopN to Vector Search",
609 vec![CorrelatedTopNToVectorSearchRule::create(false)],
610 ApplyOrder::BottomUp,
611 )
612 });
613
614static BATCH_MV_SELECTION: LazyLock<OptimizationStage> = LazyLock::new(|| {
615 OptimizationStage::new(
616 "Batch Mv Selection",
617 vec![MvSelectionRule::create()],
618 ApplyOrder::TopDown,
619 )
620});
621
622impl LogicalOptimizer {
623 pub fn predicate_pushdown(
624 plan: LogicalPlanRef,
625 explain_trace: bool,
626 ctx: &OptimizerContextRef,
627 ) -> LogicalPlanRef {
628 let mut pushdown_ctx = PredicatePushdownContext::new(plan.clone());
631 let plan = plan.predicate_pushdown(Condition::true_cond(), &mut pushdown_ctx);
632 if explain_trace {
633 ctx.trace("Predicate Push Down:");
634 ctx.trace(plan.explain_to_string());
635 }
636 plan
637 }
638
639 pub fn subquery_unnesting(
640 mut plan: LogicalPlanRef,
641 enable_share_plan: bool,
642 explain_trace: bool,
643 ctx: &OptimizerContextRef,
644 ) -> Result<LogicalPlanRef> {
645 if !has_logical_apply(plan.clone()) {
647 return Ok(plan);
648 }
649 plan = plan.optimize_by_rules(&SIMPLE_UNNESTING)?;
651 debug_assert!(!HasMaxOneRowApply().visit(plan.clone()));
652 plan = Self::predicate_pushdown(plan, explain_trace, ctx);
655 plan = plan.optimize_by_rules(&VALUES_EXTRACT_PROJECT)?;
657 plan = if enable_share_plan {
661 plan.optimize_by_rules(&GENERAL_UNNESTING_TRANS_APPLY_WITH_SHARE)?
662 } else {
663 plan.optimize_by_rules(&GENERAL_UNNESTING_TRANS_APPLY_WITHOUT_SHARE)?
664 };
665 plan = plan.optimize_by_rules_until_fix_point(&GENERAL_UNNESTING_PUSH_DOWN_APPLY)?;
666
667 plan.check_apply_elimination()?;
669
670 Ok(plan)
671 }
672
673 pub fn column_pruning(
674 mut plan: LogicalPlanRef,
675 explain_trace: bool,
676 ctx: &OptimizerContextRef,
677 ) -> LogicalPlanRef {
678 let required_cols = (0..plan.schema().len()).collect_vec();
679 let mut column_pruning_ctx = ColumnPruningContext::new(plan.clone());
682 plan = plan.prune_col(&required_cols, &mut column_pruning_ctx);
683 if explain_trace {
685 ctx.trace("Prune Columns:");
686 ctx.trace(plan.explain_to_string());
687 }
688 plan
689 }
690
691 pub fn inline_now_proc_time(plan: LogicalPlanRef, ctx: &OptimizerContextRef) -> LogicalPlanRef {
692 let mut v = NowProcTimeFinder::default();
694 plan.visit_exprs_recursive(&mut v);
695 if !v.has() {
696 return plan;
697 }
698
699 let mut v = ctx.session_ctx().pinned_snapshot().inline_now_proc_time();
700
701 let plan = plan.rewrite_exprs_recursive(&mut v);
702
703 if ctx.is_explain_trace() {
704 ctx.trace("Inline Now and ProcTime:");
705 ctx.trace(plan.explain_to_string());
706 }
707 plan
708 }
709
710 pub fn gen_optimized_logical_plan_for_stream(
711 mut plan: LogicalPlanRef,
712 ) -> Result<LogicalPlanRef> {
713 let ctx = plan.ctx();
714 let explain_trace = ctx.is_explain_trace();
715
716 if explain_trace {
717 ctx.trace("Begin:");
718 ctx.trace(plan.explain_to_string());
719 }
720
721 plan = plan.optimize_by_rules(&GROUPING_SETS)?;
723 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
725 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
727
728 let enable_share_plan = ctx.session_ctx().config().enable_share_plan();
732 if enable_share_plan {
733 plan = plan.common_subplan_sharing();
735 plan = plan.prune_share();
736 if explain_trace {
737 ctx.trace("Common Sub-plan Sharing:");
738 ctx.trace(plan.explain_to_string());
739 }
740 } else {
741 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
742
743 plan = ShareSourceRewriter::share_source(plan);
747 if explain_trace {
748 ctx.trace("Share Source:");
749 ctx.trace(plan.explain_to_string());
750 }
751 }
752 plan = plan.optimize_by_rules(&SET_OPERATION_MERGE)?;
753 plan = plan.optimize_by_rules(&SET_OPERATION_TO_JOIN)?;
754 plan = plan.optimize_by_rules(&STREAM_GENERATE_SERIES_WITH_NOW)?;
757 plan = plan.optimize_by_rules(&TABLE_FUNCTION_CONVERT)?;
759
760 plan = plan.optimize_by_rules(&CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_STREAM)?;
761
762 plan = Self::subquery_unnesting(plan, enable_share_plan, explain_trace, &ctx)?;
763 if has_logical_max_one_row(plan.clone()) {
764 bail!("Scalar subquery might produce more than one row.");
768 }
769
770 plan = plan.optimize_by_rules(&LOGICAL_FILTER_EXPRESSION_SIMPLIFY)?;
773
774 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
776
777 if plan.ctx().session_ctx().config().enable_join_ordering() {
778 plan = plan.optimize_by_rules(&TO_MULTI_JOIN)?;
782
783 if plan
785 .ctx()
786 .session_ctx()
787 .config()
788 .streaming_enable_bushy_join()
789 {
790 plan = plan.optimize_by_rules(&BUSHY_TREE_JOIN_ORDERING)?;
791 } else {
792 plan = plan.optimize_by_rules(&LEFT_DEEP_JOIN_ORDERING)?;
793 }
794 }
795
796 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
799
800 plan = plan.optimize_by_rules(&FILTER_WITH_NOW_TO_JOIN)?;
802
803 plan = plan.optimize_by_rules(&PUSH_CALC_OF_JOIN)?;
805
806 plan = plan.optimize_by_rules(&SPLIT_OVER_WINDOW)?;
807 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
810 plan = plan.optimize_by_rules(&CONVERT_OVER_WINDOW)?;
811 plan = plan.optimize_by_rules(&MERGE_OVER_WINDOW)?;
812
813 let force_split_distinct_agg = ctx.session_ctx().config().force_split_distinct_agg();
814 plan = if force_split_distinct_agg {
817 plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_BATCH)?
818 } else {
819 plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_STREAM)?
820 };
821
822 plan = plan.optimize_by_rules(&SIMPLIFY_AGG)?;
823
824 plan = plan.optimize_by_rules(&JOIN_COMMUTE)?;
825
826 plan = Self::column_pruning(plan, explain_trace, &ctx);
828 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
829
830 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
831 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
832
833 plan = plan.optimize_by_rules(&COMMON_SUB_EXPR_EXTRACT)?;
834
835 #[cfg(debug_assertions)]
836 InputRefValidator.validate(plan.clone());
837
838 ctx.may_store_explain_logical(&plan);
839
840 Ok(plan)
841 }
842
843 pub fn gen_optimized_logical_plan_for_batch(
844 mut plan: LogicalPlanRef,
845 ) -> Result<LogicalPlanRef> {
846 let ctx = plan.ctx();
847 let explain_trace = ctx.is_explain_trace();
848
849 if explain_trace {
850 ctx.trace("Begin:");
851 ctx.trace(plan.explain_to_string());
852 }
853
854 if ctx.session_ctx().config().enable_mv_selection() {
855 let query_relations =
856 RelationCollectorVisitor::collect_with(HashSet::new(), plan.clone());
857 Self::register_batch_mview_candidates(ctx.session_ctx(), &ctx, &query_relations);
858 plan = plan.optimize_by_rules(&BATCH_MV_SELECTION)?;
859 }
860
861 plan = Self::inline_now_proc_time(plan, &ctx);
863
864 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
866
867 plan = plan.optimize_by_rules(&REWRITE_SOURCE_FOR_BATCH)?;
868 plan = plan.optimize_by_rules(&GROUPING_SETS)?;
869 plan = plan.optimize_by_rules(&REWRITE_LIKE_EXPR)?;
870 plan = plan.optimize_by_rules(&SET_OPERATION_MERGE)?;
871 plan = plan.optimize_by_rules(&SET_OPERATION_TO_JOIN)?;
872 plan = plan.optimize_by_rules(&ALWAYS_FALSE_FILTER)?;
873 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_FILE_SCAN)?;
875 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_POSTGRES_QUERY)?;
876 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_MYSQL_QUERY)?;
877 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_MSSQL_QUERY)?;
878 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_BACKFILL_PROGRESS)?;
879 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_GET_CHANNEL_DELTA_STATS)?;
880 plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_INTERNAL_SOURCE_BACKFILL_PROGRESS)?;
881 plan = plan.optimize_by_rules(&TABLE_FUNCTION_CONVERT)?;
883
884 plan = plan.optimize_by_rules(&CORRELATED_TOP_N_TO_VECTOR_SEARCH_FOR_BATCH)?;
885
886 plan = Self::subquery_unnesting(plan, false, explain_trace, &ctx)?;
887
888 plan = plan.optimize_by_rules(&LOGICAL_FILTER_EXPRESSION_SIMPLIFY)?;
892
893 let mut last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
895 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
896
897 if plan.ctx().session_ctx().config().enable_join_ordering() {
898 plan = plan.optimize_by_rules(&TO_MULTI_JOIN)?;
902
903 plan = plan.optimize_by_rules(&LEFT_DEEP_JOIN_ORDERING)?;
905 }
906
907 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
910 last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
911 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
912 }
913
914 plan = plan.optimize_by_rules(&PUSH_CALC_OF_JOIN)?;
916
917 plan = plan.optimize_by_rules(&SPLIT_OVER_WINDOW)?;
918 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
921 last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied();
922 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
923 }
924 plan = plan.optimize_by_rules(&CONVERT_OVER_WINDOW_FOR_BATCH)?;
925 plan = plan.optimize_by_rules(&MERGE_OVER_WINDOW)?;
926
927 plan = plan.optimize_by_rules(&CONVERT_DISTINCT_AGG_FOR_BATCH)?;
929
930 plan = plan.optimize_by_rules(&SIMPLIFY_AGG)?;
931
932 plan = plan.optimize_by_rules(&JOIN_COMMUTE)?;
933
934 plan = plan.optimize_by_rules(&TOP_N_TO_VECTOR_SEARCH)?;
935
936 plan = Self::column_pruning(plan, explain_trace, &ctx);
938 if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() {
939 let _ = ctx.total_rule_applied();
940 plan = Self::predicate_pushdown(plan, explain_trace, &ctx);
941 }
942
943 plan = plan.optimize_by_rules(&MATERIALIZE_ICEBERG_SCAN)?;
945
946 plan = plan.optimize_by_rules(&CONSTANT_OUTPUT_REMOVE)?;
947 plan = plan.optimize_by_rules(&PROJECT_REMOVE)?;
948
949 plan = plan.optimize_by_rules(&COMMON_SUB_EXPR_EXTRACT)?;
950
951 plan = plan.optimize_by_rules(&ICEBERG_COUNT_STAR)?;
953
954 plan = plan.optimize_by_rules(&PULL_UP_HOP)?;
955
956 plan = plan.optimize_by_rules(&TOP_N_AGG_ON_INDEX)?;
957
958 plan = plan.optimize_by_rules(&PROJECT_TOP_N_TRANSPOSE)?;
959
960 plan = plan.optimize_by_rules(&LIMIT_PUSH_DOWN)?;
961
962 plan = plan.optimize_by_rules(&DAG_TO_TREE)?;
963
964 #[cfg(debug_assertions)]
965 InputRefValidator.validate(plan.clone());
966
967 ctx.may_store_explain_logical(&plan);
968
969 Ok(plan)
970 }
971
972 fn register_batch_mview_candidates(
973 session: &SessionImpl,
974 context: &OptimizerContextRef,
975 query_relations: &HashSet<ObjectId>,
976 ) {
977 let catalog_reader = session.env().catalog_reader().read_guard();
978 let user_reader = session.env().user_info_reader().read_guard();
979 let Some(current_user) = user_reader.get_user_by_name(&session.user_name()) else {
980 return;
981 };
982 let mut mv_dependencies: HashMap<ObjectId, HashSet<ObjectId>> = HashMap::new();
983 let mut mviews_with_source_dependency: HashSet<ObjectId> = HashSet::new();
984 for dep in catalog_reader.iter_object_dependencies() {
985 mv_dependencies
986 .entry(dep.object_id)
987 .or_default()
988 .insert(dep.referenced_object_id);
989 if dep.referenced_object_type == PbObjectType::Source {
990 mviews_with_source_dependency.insert(dep.object_id);
991 }
992 }
993 let db_name = session.database();
994 let Ok(schemas) = catalog_reader.iter_schemas(&db_name) else {
995 return;
996 };
997
998 for schema in schemas {
999 for mv in schema.iter_created_mvs_with_acl(current_user) {
1000 let mv_object_id = mv.id().as_object_id();
1001 if mviews_with_source_dependency.contains(&mv_object_id) {
1002 continue;
1003 }
1004 let is_subset = mv_dependencies
1005 .get(&mv_object_id)
1006 .is_some_and(|deps| deps.is_subset(query_relations));
1007 if !is_subset {
1008 continue;
1009 }
1010 let Ok(stmt) = mv.create_sql_ast() else {
1011 continue;
1012 };
1013 let Statement::CreateView {
1014 materialized: true,
1015 query,
1016 ..
1017 } = stmt
1018 else {
1019 continue;
1020 };
1021 let mut binder = Binder::new_for_batch(session);
1022 let Ok(bound_query) = binder.bind_query(&query) else {
1023 continue;
1024 };
1025 let mut planner = Planner::new_for_batch_dql(context.clone());
1026 let Ok(plan_root) = planner.plan_query(bound_query) else {
1027 continue;
1028 };
1029 context.add_batch_mview_candidate(mv.clone(), plan_root.plan.clone());
1030 }
1031 }
1032 }
1033}