From e05b193f10ed47543377eaef06789df1198b9e3b Mon Sep 17 00:00:00 2001 From: Alex Chi Z Date: Thu, 19 Dec 2024 16:12:09 -0500 Subject: [PATCH] feat(core): add structured trace Signed-off-by: Alex Chi Z --- optd-core/src/cascades/memo.rs | 11 +- optd-core/src/cascades/optimizer.rs | 124 +++++++++++++++--- optd-core/src/cascades/tasks2.rs | 72 ++++++++-- optd-datafusion-repr/src/lib.rs | 1 + optd-sqlplannertest/src/lib.rs | 41 +++--- .../tests/utils/memo_dump.planner.sql | 107 +++++++++++++++ optd-sqlplannertest/tests/utils/memo_dump.yml | 12 ++ 7 files changed, 317 insertions(+), 51 deletions(-) create mode 100644 optd-sqlplannertest/tests/utils/memo_dump.planner.sql create mode 100644 optd-sqlplannertest/tests/utils/memo_dump.yml diff --git a/optd-core/src/cascades/memo.rs b/optd-core/src/cascades/memo.rs index 85120832..1ee62544 100644 --- a/optd-core/src/cascades/memo.rs +++ b/optd-core/src/cascades/memo.rs @@ -113,6 +113,9 @@ pub trait Memo: 'static + Send + Sync { /// The group id is volatile, depending on whether the groups are merged. fn get_group_id(&self, expr_id: ExprId) -> GroupId; + /// Reduce the group ID to the merged group ID. + fn reduce_group(&self, group_id: GroupId) -> GroupId; + /// Get the memoized representation of a node. fn get_expr_memoed(&self, expr_id: ExprId) -> ArcMemoPlanNode; @@ -315,6 +318,10 @@ impl Memo for NaiveMemo { fn estimated_plan_space(&self) -> usize { self.expr_id_to_expr_node.len() } + + fn reduce_group(&self, group_id: GroupId) -> GroupId { + self.merged_group_mapping[&group_id] + } } impl NaiveMemo { @@ -396,10 +403,6 @@ impl NaiveMemo { } } - fn reduce_group(&self, group_id: GroupId) -> GroupId { - self.merged_group_mapping[&group_id] - } - fn merge_group_inner(&mut self, merge_into: GroupId, merge_from: GroupId) { if merge_into == merge_from { return; diff --git a/optd-core/src/cascades/optimizer.rs b/optd-core/src/cascades/optimizer.rs index ff0b8e8a..c97c0a53 100644 --- a/optd-core/src/cascades/optimizer.rs +++ b/optd-core/src/cascades/optimizer.rs @@ -10,9 +10,10 @@ use std::pin::Pin; use std::sync::Arc; use anyhow::Result; +use itertools::Itertools; use tracing::trace; -use super::memo::{ArcMemoPlanNode, GroupInfo, Memo}; +use super::memo::{ArcMemoPlanNode, GroupInfo, Memo, WinnerInfo}; use super::NaiveMemo; use crate::cascades::memo::Winner; use crate::cascades::tasks2::{TaskContext, TaskDesc}; @@ -38,6 +39,7 @@ pub struct OptimizerContext { #[derive(Default, Clone, Debug)] pub struct OptimizerProperties { + /// Panic the optimizer if the budget is reached, used in planner tests. pub panic_on_budget: bool, /// If the number of rules applied exceeds this number, we stop applying logical rules. pub partial_explore_iter: Option, @@ -45,9 +47,84 @@ pub struct OptimizerProperties { pub partial_explore_space: Option, /// Disable pruning during optimization. pub disable_pruning: bool, + /// Enable tracing during optimization. + pub enable_tracing: bool, } -#[derive(Debug, Default)] +#[derive(Clone)] +pub enum OptimizerTrace { + /// A winner decision is made + DecideWinner { + /// The stage and step number when a trace is recorded + stage: usize, + step: usize, + /// The group ID when a trace is recorded + group_id: GroupId, + /// The proposed winner + proposed_winner_info: WinnerInfo, + /// The winner of the children + children_winner: Vec, + }, + /// The group is created by applying a rule + ApplyRule { + /// The step number when a trace is recorded + stage: usize, + step: usize, + /// The group ID when a trace is recorded + group_id: GroupId, + /// The expression being applied + applied_expr_id: ExprId, + /// The expression being produced + produced_expr_id: ExprId, + /// The rule ID + rule_id: usize, + }, +} + +impl OptimizerTrace { + pub fn stage_step(&self) -> (usize, usize) { + match self { + OptimizerTrace::DecideWinner { stage, step, .. } => (*stage, *step), + OptimizerTrace::ApplyRule { stage, step, .. } => (*stage, *step), + } + } +} + +impl std::fmt::Display for OptimizerTrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OptimizerTrace::DecideWinner { + stage, + step, + group_id, + proposed_winner_info, + children_winner, + } => { + write!( + f, + "step={}/{} decide_winner group_id={} proposed_winner_expr={} children_winner_exprs=[{}] total_weighted_cost={}", + stage, step, group_id, proposed_winner_info.expr_id, children_winner.iter().join(","), proposed_winner_info.total_weighted_cost + ) + } + OptimizerTrace::ApplyRule { + stage, + step, + group_id, + applied_expr_id, + produced_expr_id, + rule_id, + } => { + write!( + f, + "step={}/{} apply_rule group_id={} applied_expr_id={} produced_expr_id={} rule_id={}", + stage, step, group_id, applied_expr_id, produced_expr_id, rule_id + ) + } + } + } +} + +#[derive(Default)] pub struct CascadesStats { pub rule_match_count: HashMap, pub rule_total_bindings: HashMap, @@ -56,6 +133,7 @@ pub struct CascadesStats { pub optimize_expr_count: usize, pub apply_rule_count: usize, pub optimize_input_count: usize, + pub trace: HashMap>, } pub struct CascadesOptimizer = NaiveMemo> { @@ -70,6 +148,7 @@ pub struct CascadesOptimizer = NaiveMemo> { logical_property_builders: Arc<[Box>]>, pub ctx: OptimizerContext, pub prop: OptimizerProperties, + stage: usize, } /// `RelNode` only contains the representation of the plan nodes. Sometimes, we need more context, @@ -137,6 +216,7 @@ impl CascadesOptimizer> { prop, stats: CascadesStats::default(), disabled_rules: HashSet::new(), + stage: 0, } } @@ -163,14 +243,6 @@ impl CascadesOptimizer> { } impl> CascadesOptimizer { - pub fn panic_on_explore_limit(&mut self, enabled: bool) { - self.prop.panic_on_budget = enabled; - } - - pub fn disable_pruning(&mut self, enabled: bool) { - self.prop.disable_pruning = enabled; - } - pub fn cost(&self) -> Arc> { self.cost.clone() } @@ -217,7 +289,7 @@ impl> CascadesOptimizer { self.disabled_rules.contains(&rule_id) } - pub fn dump(&self) { + pub fn dump(&self, mut f: impl std::fmt::Write) -> std::fmt::Result { for group_id in self.memo.get_all_group_ids() { let winner_str = match &self.memo.get_group_info(group_id).winner { Winner::Impossible => "winner=".to_string(), @@ -234,14 +306,15 @@ impl> CascadesOptimizer { ) } }; - println!("group_id={} {}", group_id, winner_str); + writeln!(f, "group_id={} {}", group_id, winner_str)?; let group = self.memo.get_group(group_id); for (id, property) in self.logical_property_builders.iter().enumerate() { - println!( + writeln!( + f, " {}={}", property.property_name(), group.properties[id].as_ref() - ) + )?; } let mut all_predicates = BTreeSet::new(); for expr_id in self.memo.get_all_exprs_in_group(group_id) { @@ -249,13 +322,25 @@ impl> CascadesOptimizer { for pred in &memo_node.predicates { all_predicates.insert(*pred); } - println!(" expr_id={} | {}", expr_id, memo_node); + writeln!(f, " expr_id={} | {}", expr_id, memo_node)?; } for pred in all_predicates { - println!(" {}={}", pred, self.memo.get_pred(pred)); + writeln!(f, " {}={}", pred, self.memo.get_pred(pred))?; + } + let mut traces = Vec::new(); + for (that_group_id, trace) in &self.stats.trace { + if self.memo.reduce_group(*that_group_id) == group_id { + traces.extend(trace.iter()); + } + } + traces.sort_by_key(|x| x.stage_step()); + for t in traces { + writeln!(f, " {}", t)?; } } + Ok(()) } + /// Optimize a `RelNode`. pub fn step_optimize_rel(&mut self, root_rel: ArcPlanNode) -> Result { trace!(event = "step_optimize_rel", rel = %root_rel); @@ -287,7 +372,9 @@ impl> CascadesOptimizer { } }); if res.is_err() && cfg!(debug_assertions) { - self.dump(); + let mut buf = String::new(); + self.dump(&mut buf).unwrap(); + eprintln!("{}", buf); } res } @@ -295,7 +382,8 @@ impl> CascadesOptimizer { pub fn fire_optimize_tasks(&mut self, group_id: GroupId) -> Result<()> { use pollster::FutureExt as _; trace!(event = "fire_optimize_tasks", root_group_id = %group_id); - let mut task = TaskContext::new(self); + self.stage += 1; + let mut task = TaskContext::new(self, self.stage); // 32MB stack for the optimization process, TODO: reduce memory footprint stacker::grow(32 * 1024 * 1024, || { let fut: Pin>> = Box::pin(task.fire_optimize(group_id)); diff --git a/optd-core/src/cascades/tasks2.rs b/optd-core/src/cascades/tasks2.rs index a1961804..b7c81a06 100644 --- a/optd-core/src/cascades/tasks2.rs +++ b/optd-core/src/cascades/tasks2.rs @@ -6,6 +6,7 @@ use tracing::trace; use super::memo::MemoPlanNode; use super::rule_match::match_and_pick_expr; use super::{optimizer::RuleId, CascadesOptimizer, ExprId, GroupId, Memo}; +use crate::cascades::optimizer::OptimizerTrace; use crate::cascades::{ memo::{Winner, WinnerInfo}, RelNodeContext, @@ -21,7 +22,12 @@ struct SearchContext { pub struct TaskContext<'a, T: NodeType, M: Memo> { optimizer: &'a mut CascadesOptimizer, + /// The stage of the process + stage: usize, + /// Number of tasks fired, used to determine the explore budget steps: usize, + /// Counter of trace produced, used in the traces + trace_steps: usize, } /// Ensures we don't run into cycles / dead loops. @@ -32,14 +38,17 @@ pub enum TaskDesc { } impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { - pub fn new(optimizer: &'a mut CascadesOptimizer) -> Self { + pub fn new(optimizer: &'a mut CascadesOptimizer, stage: usize) -> Self { Self { + stage, optimizer, steps: 0, + trace_steps: 0, } } pub async fn fire_optimize(&mut self, group_id: GroupId) { + tracing::debug!(event = "fire_optimize", group_id = %group_id); self.optimize_group(SearchContext { group_id, upper_bound: None, @@ -323,7 +332,25 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { for expr in applied { trace!(event = "after_apply_rule", task = "apply_rule", output_binding=%expr); // TODO: remove clone in the below line - if let Some(expr_id) = self.optimizer.add_expr_to_group(expr.clone(), group_id) { + if let Some(produced_expr_id) = + self.optimizer.add_expr_to_group(expr.clone(), group_id) + { + if self.optimizer.prop.enable_tracing { + self.trace_steps += 1; + self.optimizer + .stats + .trace + .entry(group_id) + .or_default() + .push(OptimizerTrace::ApplyRule { + stage: self.stage, + step: self.trace_steps, + group_id, + applied_expr_id: expr_id, + produced_expr_id, + rule_id, + }); + } let typ = expr.unwrap_typ(); if typ.is_logical() { self.optimize_expr( @@ -331,7 +358,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { group_id, upper_bound: ctx.upper_bound, }, - expr_id, + produced_expr_id, exploring, ) .await; @@ -341,7 +368,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { group_id, upper_bound: ctx.upper_bound, }, - expr_id, + produced_expr_id, ) .await; } @@ -384,7 +411,13 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { expr_id: ExprId, expr: &MemoPlanNode, predicates: &[ArcPredNode], - ) -> (Vec>>, Vec, Cost, Cost) { + ) -> ( + Vec>>, + Vec, + Cost, + Cost, + Vec>, + ) { let context = RelNodeContext { expr_id, group_id, @@ -392,6 +425,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { }; let mut input_stats = Vec::with_capacity(expr.children.len()); let mut input_cost = Vec::with_capacity(expr.children.len()); + let mut children_winners = Vec::with_capacity(expr.children.len()); let cost = self.optimizer.cost(); #[allow(clippy::needless_range_loop)] for idx in 0..expr.children.len() { @@ -406,6 +440,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { .map(|x| x.total_cost.clone()) .unwrap_or_else(|| cost.zero()), ); + children_winners.push(winner.map(|x| x.expr_id)); } let input_stats_ref = input_stats .iter() @@ -419,7 +454,13 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { self.optimizer, ); let total_cost = cost.sum(&operation_cost, &input_cost); - (input_stats, input_cost, total_cost, operation_cost) + ( + input_stats, + input_cost, + total_cost, + operation_cost, + children_winners, + ) } async fn optimize_input_inner(&mut self, ctx: SearchContext, expr_id: ExprId) { @@ -465,7 +506,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { for (input_group_idx, _) in expr.children.iter().enumerate() { // Before optimizing each of the child, infer a current lower bound cost - let (_, input_costs, total_cost, _) = + let (_, input_costs, total_cost, _, _) = self.gather_statistics_and_costs(group_id, expr_id, &expr, &predicates); let child_upper_bound = if !self.optimizer.prop.disable_pruning { @@ -513,7 +554,7 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { } // Compute everything again - let (input_stats, _, total_cost, operation_cost) = + let (input_stats, _, total_cost, operation_cost, children_winner) = self.gather_statistics_and_costs(group_id, expr_id, &expr, &predicates); let input_stats_ref = input_stats .iter() @@ -542,6 +583,21 @@ impl<'a, T: NodeType, M: Memo> TaskContext<'a, T, M> { operation_weighted_cost: cost.weighted_cost(&operation_cost), statistics, }; + if self.optimizer.prop.enable_tracing { + self.trace_steps += 1; + self.optimizer + .stats + .trace + .entry(group_id) + .or_default() + .push(OptimizerTrace::DecideWinner { + stage: self.stage, + step: self.trace_steps, + group_id, + proposed_winner_info: proposed_winner.clone(), + children_winner: children_winner.into_iter().map(|x| x.unwrap()).collect(), + }); + } self.update_winner_if_better(group_id, proposed_winner); trace!(event = "task_finish", task = "optimize_inputs", expr_id = %expr_id, result = "resolved"); self.optimizer.mark_task_end(&desc); diff --git a/optd-datafusion-repr/src/lib.rs b/optd-datafusion-repr/src/lib.rs index d7d17eec..d101b0c4 100644 --- a/optd-datafusion-repr/src/lib.rs +++ b/optd-datafusion-repr/src/lib.rs @@ -154,6 +154,7 @@ impl DatafusionOptimizer { partial_explore_iter: Some(1 << 18), partial_explore_space: Some(1 << 14), disable_pruning: false, + enable_tracing: false, }, ), heuristic_optimizer: HeuristicsOptimizer::new_with_rules( diff --git a/optd-sqlplannertest/src/lib.rs b/optd-sqlplannertest/src/lib.rs index 3ea88975..9b78e904 100644 --- a/optd-sqlplannertest/src/lib.rs +++ b/optd-sqlplannertest/src/lib.rs @@ -91,16 +91,10 @@ impl DatafusionDBMS { .lock() .unwrap(); let optimizer = guard.as_mut().unwrap().optd_optimizer_mut(); - if flags.panic_on_budget { - optimizer.panic_on_explore_limit(true); - } else { - optimizer.panic_on_explore_limit(false); - } - if flags.disable_pruning { - optimizer.disable_pruning(true); - } else { - optimizer.disable_pruning(false); - } + + optimizer.prop.panic_on_budget = flags.panic_on_budget; + optimizer.prop.enable_tracing = flags.enable_tracing; + optimizer.prop.disable_pruning = flags.disable_pruning; let rules = optimizer.rules(); if flags.enable_logical_rules.is_empty() { for r in 0..rules.len() { @@ -201,17 +195,6 @@ impl DatafusionDBMS { } } } - if flags.dump_memo_table { - let mut guard = self - .optd_optimizer - .as_ref() - .unwrap() - .optimizer - .lock() - .unwrap(); - let optimizer = guard.as_mut().unwrap().optd_optimizer_mut(); - optimizer.dump(); - } Ok(result) } @@ -333,6 +316,19 @@ impl sqlplannertest::PlannerTestRunner for DatafusionDBMS { } else if task.starts_with("explain") { self.task_explain(r, &test_case.sql, task, &flags).await?; } + if flags.dump_memo_table { + let mut guard = self + .optd_optimizer + .as_ref() + .unwrap() + .optimizer + .lock() + .unwrap(); + let optimizer = guard.as_mut().unwrap().optd_optimizer_mut(); + let mut buf = String::new(); + optimizer.dump(&mut buf).unwrap(); + r.push_str(&buf); + } } Ok(result) } @@ -348,6 +344,7 @@ pub struct TestFlags { enable_df_logical: bool, enable_logical_rules: Vec, panic_on_budget: bool, + enable_tracing: bool, dump_memo_table: bool, disable_pruning: bool, } @@ -382,6 +379,8 @@ pub fn extract_flags(task: &str) -> Result { options.dump_memo_table = true; } else if flag == "disable_pruning" { options.disable_pruning = true; + } else if flag == "enable_tracing" { + options.enable_tracing = true; } else { bail!("Unknown flag: {}", flag); } diff --git a/optd-sqlplannertest/tests/utils/memo_dump.planner.sql b/optd-sqlplannertest/tests/utils/memo_dump.planner.sql new file mode 100644 index 00000000..2d17f680 --- /dev/null +++ b/optd-sqlplannertest/tests/utils/memo_dump.planner.sql @@ -0,0 +1,107 @@ +-- (no id or description) +create table t1(t1v1 int, t1v2 int); +create table t2(t2v1 int, t2v3 int); +insert into t1 values (0, 0), (1, 1), (2, 2); +insert into t2 values (0, 200), (1, 201), (2, 202); + +/* +3 +3 +*/ + +-- test self join +select * from t1 as a, t1 as b where a.t1v1 = b.t1v1 order by a.t1v1; + +/* +PhysicalSort +├── exprs:SortOrder { order: Asc } +│ └── #0 +└── PhysicalHashJoin { join_type: Inner, left_keys: [ #0 ], right_keys: [ #0 ] } + ├── PhysicalScan { table: t1 } + └── PhysicalScan { table: t1 } +group_id=!2 winner=23 weighted_cost=1000 cost={compute=0,io=1000} stat={row_cnt=1000} | (PhysicalScan P0) + schema=[t1v1:Int32, t1v2:Int32] + column_ref=[t1.0, t1.1] + expr_id=1 | (Scan P0) + expr_id=23 | (PhysicalScan P0) + P0=(Constant(Utf8String) "t1") + step=1/6 apply_rule group_id=!2 applied_expr_id=1 produced_expr_id=23 rule_id=0 + step=1/7 decide_winner group_id=!2 proposed_winner_expr=23 children_winner_exprs=[] total_weighted_cost=1000 + step=2/1 decide_winner group_id=!2 proposed_winner_expr=23 children_winner_exprs=[] total_weighted_cost=1000 +group_id=!6 winner=21 weighted_cost=1003000 cost={compute=1001000,io=2000} stat={row_cnt=10000} | (PhysicalNestedLoopJoin(Inner) !2 !2 P4) + schema=[t1v1:Int32, t1v2:Int32, t1v1:Int32, t1v2:Int32] + column_ref=[t1.0, t1.1, t1.0, t1.1] + expr_id=5 | (Join(Inner) !2 !2 P4) + expr_id=21 | (PhysicalNestedLoopJoin(Inner) !2 !2 P4) + expr_id=42 | (Projection !6 P32) + expr_id=49 | (Projection !6 P37) + P4=(Constant(Bool) true) + P32=(List (ColumnRef 2(u64)) (ColumnRef 3(u64)) (ColumnRef 0(u64)) (ColumnRef 1(u64))) + P37=(List (ColumnRef 0(u64)) (ColumnRef 1(u64)) (ColumnRef 2(u64)) (ColumnRef 3(u64))) + step=1/1 apply_rule group_id=!6 applied_expr_id=5 produced_expr_id=5 rule_id=19 + step=1/5 apply_rule group_id=!6 applied_expr_id=5 produced_expr_id=21 rule_id=2 + step=1/8 decide_winner group_id=!6 proposed_winner_expr=21 children_winner_exprs=[23,23] total_weighted_cost=1003000 + step=2/9 apply_rule group_id=!6 applied_expr_id=5 produced_expr_id=42 rule_id=13 + step=2/10 apply_rule group_id=!6 applied_expr_id=42 produced_expr_id=49 rule_id=17 + step=2/11 apply_rule group_id=!6 applied_expr_id=49 produced_expr_id=42 rule_id=17 + step=2/12 apply_rule group_id=!6 applied_expr_id=49 produced_expr_id=49 rule_id=17 +group_id=!12 winner=17 weighted_cost=11908.75477931522 cost={compute=9908.75477931522,io=2000} stat={row_cnt=1000} | (PhysicalSort !31 P10) + schema=[t1v1:Int32, t1v2:Int32, t1v1:Int32, t1v2:Int32] + column_ref=[t1.0, t1.1, t1.0, t1.1] + expr_id=11 | (Sort !31 P10) + expr_id=17 | (PhysicalSort !31 P10) + P10=(List (SortOrder(Asc) (ColumnRef 0(u64)))) + step=1/3 apply_rule group_id=!12 applied_expr_id=11 produced_expr_id=17 rule_id=4 + step=1/13 decide_winner group_id=!12 proposed_winner_expr=17 children_winner_exprs=[28] total_weighted_cost=11908.75477931522 + step=2/28 decide_winner group_id=!12 proposed_winner_expr=17 children_winner_exprs=[28] total_weighted_cost=11908.75477931522 +group_id=!31 winner=28 weighted_cost=5000 cost={compute=3000,io=2000} stat={row_cnt=1000} | (PhysicalHashJoin(Inner) !2 !2 P26 P26) + schema=[t1v1:Int32, t1v2:Int32, t1v1:Int32, t1v2:Int32] + column_ref=[t1.0, t1.1, t1.0, t1.1] + expr_id=8 | (Filter !6 P7) + expr_id=15 | (Join(Inner) !2 !2 P7) + expr_id=19 | (PhysicalFilter !6 P7) + expr_id=25 | (PhysicalNestedLoopJoin(Inner) !2 !2 P7) + expr_id=28 | (PhysicalHashJoin(Inner) !2 !2 P26 P26) + expr_id=30 | (Join(Inner) !2 !2 P29) + expr_id=33 | (Projection !31 P32) + expr_id=38 | (Projection !31 P37) + expr_id=45 | (Filter !6 P29) + expr_id=58 | (PhysicalProjection !31 P32) + expr_id=60 | (PhysicalNestedLoopJoin(Inner) !2 !2 P29) + expr_id=71 | (PhysicalProjection !31 P37) + expr_id=73 | (PhysicalFilter !6 P29) + P7=(BinOp(Eq) (ColumnRef 0(u64)) (ColumnRef 2(u64))) + P26=(List (ColumnRef 0(u64))) + P29=(BinOp(Eq) (ColumnRef 2(u64)) (ColumnRef 0(u64))) + P32=(List (ColumnRef 2(u64)) (ColumnRef 3(u64)) (ColumnRef 0(u64)) (ColumnRef 1(u64))) + P37=(List (ColumnRef 0(u64)) (ColumnRef 1(u64)) (ColumnRef 2(u64)) (ColumnRef 3(u64))) + step=1/2 apply_rule group_id=!9 applied_expr_id=8 produced_expr_id=15 rule_id=9 + step=1/4 apply_rule group_id=!9 applied_expr_id=8 produced_expr_id=19 rule_id=3 + step=1/9 decide_winner group_id=!9 proposed_winner_expr=19 children_winner_exprs=[21] total_weighted_cost=1033000 + step=1/10 apply_rule group_id=!9 applied_expr_id=15 produced_expr_id=25 rule_id=2 + step=1/11 apply_rule group_id=!9 applied_expr_id=15 produced_expr_id=28 rule_id=12 + step=1/12 decide_winner group_id=!9 proposed_winner_expr=28 children_winner_exprs=[23,23] total_weighted_cost=5000 + step=2/2 decide_winner group_id=!9 proposed_winner_expr=28 children_winner_exprs=[23,23] total_weighted_cost=5000 + step=2/3 apply_rule group_id=!9 applied_expr_id=15 produced_expr_id=33 rule_id=13 + step=2/4 apply_rule group_id=!31 applied_expr_id=30 produced_expr_id=36 rule_id=13 + step=2/5 apply_rule group_id=!31 applied_expr_id=36 produced_expr_id=38 rule_id=17 + step=2/6 apply_rule group_id=!31 applied_expr_id=38 produced_expr_id=36 rule_id=17 + step=2/7 apply_rule group_id=!31 applied_expr_id=38 produced_expr_id=38 rule_id=17 + step=2/8 apply_rule group_id=!31 applied_expr_id=36 produced_expr_id=45 rule_id=21 + step=2/13 apply_rule group_id=!31 applied_expr_id=45 produced_expr_id=36 rule_id=8 + step=2/14 apply_rule group_id=!31 applied_expr_id=45 produced_expr_id=38 rule_id=8 + step=2/15 apply_rule group_id=!31 applied_expr_id=45 produced_expr_id=30 rule_id=9 + step=2/16 apply_rule group_id=!9 applied_expr_id=33 produced_expr_id=58 rule_id=1 + step=2/17 apply_rule group_id=!31 applied_expr_id=30 produced_expr_id=60 rule_id=2 + step=2/18 apply_rule group_id=!31 applied_expr_id=30 produced_expr_id=28 rule_id=12 + step=2/19 decide_winner group_id=!31 proposed_winner_expr=28 children_winner_exprs=[23,23] total_weighted_cost=5000 + step=2/20 apply_rule group_id=!31 applied_expr_id=33 produced_expr_id=38 rule_id=17 + step=2/21 apply_rule group_id=!31 applied_expr_id=33 produced_expr_id=33 rule_id=17 + step=2/22 apply_rule group_id=!31 applied_expr_id=33 produced_expr_id=45 rule_id=21 + step=2/23 apply_rule group_id=!31 applied_expr_id=33 produced_expr_id=8 rule_id=21 + step=2/24 apply_rule group_id=!31 applied_expr_id=36 produced_expr_id=58 rule_id=1 + step=2/25 apply_rule group_id=!31 applied_expr_id=38 produced_expr_id=71 rule_id=1 + step=2/26 apply_rule group_id=!31 applied_expr_id=45 produced_expr_id=73 rule_id=3 + step=2/27 decide_winner group_id=!9 proposed_winner_expr=58 children_winner_exprs=[28] total_weighted_cost=10000 +*/ + diff --git a/optd-sqlplannertest/tests/utils/memo_dump.yml b/optd-sqlplannertest/tests/utils/memo_dump.yml new file mode 100644 index 00000000..3ad540e0 --- /dev/null +++ b/optd-sqlplannertest/tests/utils/memo_dump.yml @@ -0,0 +1,12 @@ +- sql: | + create table t1(t1v1 int, t1v2 int); + create table t2(t2v1 int, t2v3 int); + insert into t1 values (0, 0), (1, 1), (2, 2); + insert into t2 values (0, 200), (1, 201), (2, 202); + tasks: + - execute +- sql: | + select * from t1 as a, t1 as b where a.t1v1 = b.t1v1 order by a.t1v1; + desc: test self join + tasks: + - explain[dump_memo_table,enable_tracing]:physical_optd