Skip to main content

graphrecords_query/optimizer/
engine.rs

1use super::{
2    phase::{
3        FixpointPolicy, OptimizationReport, PhaseId, PhaseLabel, PhaseOutcome, RunCondition,
4        StopReason,
5    },
6    rule::{ErasedRule, Rule, Transformed},
7    stats::Stats,
8};
9use crate::Operand;
10use graphrecords_utils::aliases::{GrHashMap, GrHashSet};
11use std::{
12    any::{Any, TypeId, type_name},
13    collections::hash_map::DefaultHasher,
14    error::Error,
15    fmt::{self, Display, Formatter},
16    hash::Hasher,
17};
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
20pub enum Direction {
21    TopDown,
22    BottomUp,
23    Manual,
24}
25
26const DIRECTION_ORDER: [Direction; 3] =
27    [Direction::TopDown, Direction::BottomUp, Direction::Manual];
28
29#[derive(Debug)]
30#[non_exhaustive]
31pub struct OptimizerError {
32    misconfigurations: Vec<Misconfiguration>,
33}
34
35impl OptimizerError {
36    #[must_use]
37    pub const fn new(misconfigurations: Vec<Misconfiguration>) -> Self {
38        Self { misconfigurations }
39    }
40
41    #[must_use]
42    pub fn misconfigurations(&self) -> &[Misconfiguration] {
43        &self.misconfigurations
44    }
45}
46
47#[derive(Debug)]
48#[non_exhaustive]
49pub enum Misconfiguration {
50    DuplicatePhase(PhaseId),
51    UnknownPhase {
52        phase: PhaseId,
53        rule: &'static str,
54    },
55    UnknownPhaseReference {
56        phase: PhaseId,
57        reference: PhaseId,
58    },
59    UnknownRuleReference {
60        phase: PhaseId,
61        rule: &'static str,
62        reference: &'static str,
63        registered_elsewhere: Option<PhaseId>,
64    },
65    UnknownExclusion(&'static str),
66    NonExcludable(&'static str),
67    PhaseCycle(Vec<PhaseId>),
68    RuleCycle {
69        phase: PhaseId,
70        rules: Vec<&'static str>,
71    },
72}
73
74impl Display for Misconfiguration {
75    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
76        match self {
77            Self::DuplicatePhase(phase) => {
78                write!(formatter, "phase {phase:?} is declared more than once")
79            }
80            Self::UnknownPhase { phase, rule } => write!(
81                formatter,
82                "rule `{rule}` targets phase {phase:?}, which is never declared"
83            ),
84            Self::UnknownPhaseReference { phase, reference } => write!(
85                formatter,
86                "phase {phase:?} orders against phase {reference:?}, which is never declared"
87            ),
88            Self::UnknownRuleReference {
89                phase,
90                rule,
91                reference,
92                registered_elsewhere,
93            } => {
94                write!(
95                    formatter,
96                    "rule `{rule}` in phase {phase:?} orders against `{reference}`, which is "
97                )?;
98
99                match registered_elsewhere {
100                    Some(other) => write!(
101                        formatter,
102                        "not registered in this phase (it is registered in phase {other:?} instead)"
103                    ),
104                    None => formatter.write_str("never registered"),
105                }
106            }
107            Self::UnknownExclusion(name) => write!(
108                formatter,
109                "exclusion targets rule `{name}`, which is never registered"
110            ),
111            Self::NonExcludable(name) => write!(
112                formatter,
113                "rule `{name}` is marked non-excludable and cannot be excluded"
114            ),
115            Self::PhaseCycle(labels) => {
116                write!(formatter, "phase ordering has a cycle involving {labels:?}")
117            }
118            Self::RuleCycle { phase, rules } => write!(
119                formatter,
120                "rule ordering in phase {phase:?} has a cycle involving {rules:?}"
121            ),
122        }
123    }
124}
125
126impl Display for OptimizerError {
127    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
128        formatter.write_str("optimizer configuration is invalid:")?;
129
130        for misconfiguration in &self.misconfigurations {
131            write!(formatter, "\n- {misconfiguration}")?;
132        }
133
134        Ok(())
135    }
136}
137
138impl Error for OptimizerError {}
139
140struct RuleEntry {
141    identity: TypeId,
142    name: &'static str,
143    operand_type: TypeId,
144    direction: Option<Direction>,
145    before: Vec<RuleIdentity>,
146    after: Vec<RuleIdentity>,
147    excludable: bool,
148    run_if: Option<RunCondition>,
149    rule: Box<dyn Any + Send + Sync>,
150}
151
152struct PendingRule {
153    phase: PhaseId,
154    entry: RuleEntry,
155}
156
157struct RuleIdentity {
158    identity: TypeId,
159    name: &'static str,
160}
161
162struct Phase {
163    id: PhaseId,
164    direction: Direction,
165    policy: FixpointPolicy,
166    run_if: Option<RunCondition>,
167    before: Vec<PhaseId>,
168    after: Vec<PhaseId>,
169    rules: Vec<RuleEntry>,
170}
171
172struct CompiledPhase {
173    id: PhaseId,
174    policy: FixpointPolicy,
175    run_if: Option<RunCondition>,
176    passes: Vec<CompiledPass>,
177}
178
179struct CompiledPass {
180    direction: Direction,
181    buckets: GrHashMap<TypeId, Vec<CompiledRule>>,
182    has_run_conditions: bool,
183}
184
185struct CompiledRule {
186    run_if: Option<RunCondition>,
187    rule: Box<dyn Any + Send + Sync>,
188}
189
190pub struct OptimizerBuilder {
191    phases: Vec<Phase>,
192    rules: Vec<PendingRule>,
193    exclusions: Vec<RuleIdentity>,
194}
195
196impl Default for OptimizerBuilder {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202impl OptimizerBuilder {
203    #[must_use]
204    pub const fn new() -> Self {
205        Self {
206            phases: Vec::new(),
207            rules: Vec::new(),
208            exclusions: Vec::new(),
209        }
210    }
211
212    pub fn add_phase(&mut self, label: impl PhaseLabel) -> PhaseHandle<'_> {
213        self.phases.push(Phase {
214            id: PhaseId::new(label),
215            direction: Direction::BottomUp,
216            policy: FixpointPolicy::fixpoint(),
217            run_if: None,
218            before: Vec::new(),
219            after: Vec::new(),
220            rules: Vec::new(),
221        });
222
223        let index = self.phases.len() - 1;
224
225        PhaseHandle {
226            builder: self,
227            index,
228        }
229    }
230
231    pub fn add_rule<O: Operand + 'static, R: Rule<O>>(
232        &mut self,
233        phase: impl PhaseLabel,
234        rule: R,
235    ) -> RuleHandle<'_> {
236        let erased: ErasedRule<_> =
237            Box::new(move |operand, session| rule.apply(operand, session.stats()));
238
239        self.rules.push(PendingRule {
240            phase: PhaseId::new(phase),
241            entry: RuleEntry {
242                identity: TypeId::of::<R>(),
243                name: type_name::<R>(),
244                operand_type: TypeId::of::<O>(),
245                direction: None,
246                before: Vec::new(),
247                after: Vec::new(),
248                excludable: true,
249                run_if: None,
250                rule: Box::new(erased),
251            },
252        });
253
254        let index = self.rules.len() - 1;
255
256        RuleHandle {
257            builder: self,
258            index,
259        }
260    }
261
262    pub fn exclude<R: 'static>(&mut self) -> &mut Self {
263        self.exclusions.push(RuleIdentity {
264            identity: TypeId::of::<R>(),
265            name: type_name::<R>(),
266        });
267        self
268    }
269
270    pub fn build(self) -> Result<Optimizer, OptimizerError> {
271        let (phase_index, duplicates) = index_phases(&self.phases);
272        let (phases, unknown_phases) = join_rules(self.phases, self.rules, &phase_index);
273        let (order, ordering_findings) = phase_order(&phases, &phase_index);
274        let reference_findings = validate_rule_references(&phases);
275        let (excluded, exclusion_findings) = resolve_exclusions(self.exclusions, &phases);
276        let (compiled_phases, cycle_findings) = compile_phases(phases, order, &excluded);
277
278        let misconfigurations: Vec<_> = duplicates
279            .into_iter()
280            .chain(unknown_phases)
281            .chain(ordering_findings)
282            .chain(reference_findings)
283            .chain(exclusion_findings)
284            .chain(cycle_findings)
285            .collect();
286
287        if !misconfigurations.is_empty() {
288            return Err(OptimizerError::new(misconfigurations));
289        }
290
291        Ok(Optimizer {
292            phases: compiled_phases,
293        })
294    }
295}
296
297pub struct PhaseHandle<'a> {
298    builder: &'a mut OptimizerBuilder,
299    index: usize,
300}
301
302impl PhaseHandle<'_> {
303    fn phase(&mut self) -> &mut Phase {
304        &mut self.builder.phases[self.index]
305    }
306
307    pub fn direction(&mut self, direction: Direction) -> &mut Self {
308        self.phase().direction = direction;
309        self
310    }
311
312    pub fn policy(&mut self, policy: FixpointPolicy) -> &mut Self {
313        self.phase().policy = policy;
314        self
315    }
316
317    pub fn once(&mut self) -> &mut Self {
318        self.policy(FixpointPolicy::Once)
319    }
320
321    pub fn fixpoint(&mut self) -> &mut Self {
322        self.policy(FixpointPolicy::fixpoint())
323    }
324
325    pub fn before(&mut self, label: impl PhaseLabel) -> &mut Self {
326        self.phase().before.push(PhaseId::new(label));
327        self
328    }
329
330    pub fn after(&mut self, label: impl PhaseLabel) -> &mut Self {
331        self.phase().after.push(PhaseId::new(label));
332        self
333    }
334
335    pub fn run_if(
336        &mut self,
337        condition: impl Fn(&Stats<'_>) -> bool + Send + Sync + 'static,
338    ) -> &mut Self {
339        self.phase().run_if = Some(Box::new(condition));
340        self
341    }
342}
343
344pub struct RuleHandle<'a> {
345    builder: &'a mut OptimizerBuilder,
346    index: usize,
347}
348
349impl RuleHandle<'_> {
350    fn entry(&mut self) -> &mut RuleEntry {
351        &mut self.builder.rules[self.index].entry
352    }
353
354    pub fn direction(&mut self, direction: Direction) -> &mut Self {
355        self.entry().direction = Some(direction);
356        self
357    }
358
359    pub fn label<L: 'static>(&mut self) -> &mut Self {
360        let entry = self.entry();
361
362        entry.identity = TypeId::of::<L>();
363        entry.name = type_name::<L>();
364
365        self
366    }
367
368    pub fn before<R: 'static>(&mut self) -> &mut Self {
369        self.entry().before.push(RuleIdentity {
370            identity: TypeId::of::<R>(),
371            name: type_name::<R>(),
372        });
373        self
374    }
375
376    pub fn after<R: 'static>(&mut self) -> &mut Self {
377        self.entry().after.push(RuleIdentity {
378            identity: TypeId::of::<R>(),
379            name: type_name::<R>(),
380        });
381        self
382    }
383
384    pub fn non_excludable(&mut self) -> &mut Self {
385        self.entry().excludable = false;
386        self
387    }
388
389    pub fn run_if(
390        &mut self,
391        condition: impl Fn(&Stats<'_>) -> bool + Send + Sync + 'static,
392    ) -> &mut Self {
393        self.entry().run_if = Some(Box::new(condition));
394        self
395    }
396}
397
398fn index_phases(phases: &[Phase]) -> (GrHashMap<PhaseId, usize>, Vec<Misconfiguration>) {
399    let mut index = GrHashMap::default();
400    let mut duplicates = Vec::new();
401
402    for (position, phase) in phases.iter().enumerate() {
403        if index.contains_key(&phase.id) {
404            duplicates.push(Misconfiguration::DuplicatePhase(phase.id.clone()));
405            continue;
406        }
407
408        index.insert(phase.id.clone(), position);
409    }
410
411    (index, duplicates)
412}
413
414fn join_rules(
415    mut phases: Vec<Phase>,
416    rules: Vec<PendingRule>,
417    phase_index: &GrHashMap<PhaseId, usize>,
418) -> (Vec<Phase>, Vec<Misconfiguration>) {
419    let mut unknown_phases = Vec::new();
420
421    for pending in rules {
422        match phase_index.get(&pending.phase) {
423            Some(&position) => phases[position].rules.push(pending.entry),
424            None => unknown_phases.push(Misconfiguration::UnknownPhase {
425                phase: pending.phase,
426                rule: pending.entry.name,
427            }),
428        }
429    }
430
431    (phases, unknown_phases)
432}
433
434fn phase_order(
435    phases: &[Phase],
436    phase_index: &GrHashMap<PhaseId, usize>,
437) -> (Option<Vec<usize>>, Vec<Misconfiguration>) {
438    let mut edges = Vec::new();
439    let mut findings = Vec::new();
440
441    for (position, phase) in phases.iter().enumerate() {
442        for reference in &phase.before {
443            match phase_index.get(reference) {
444                Some(&target) => edges.push((position, target)),
445                None => findings.push(Misconfiguration::UnknownPhaseReference {
446                    phase: phase.id.clone(),
447                    reference: reference.clone(),
448                }),
449            }
450        }
451
452        for reference in &phase.after {
453            match phase_index.get(reference) {
454                Some(&target) => edges.push((target, position)),
455                None => findings.push(Misconfiguration::UnknownPhaseReference {
456                    phase: phase.id.clone(),
457                    reference: reference.clone(),
458                }),
459            }
460        }
461    }
462
463    match toposort(phases.len(), &edges) {
464        Ok(order) => (Some(order), findings),
465        Err(cycle) => {
466            findings.push(Misconfiguration::PhaseCycle(
467                cycle
468                    .into_iter()
469                    .map(|position| phases[position].id.clone())
470                    .collect(),
471            ));
472
473            (None, findings)
474        }
475    }
476}
477
478fn validate_rule_references(phases: &[Phase]) -> Vec<Misconfiguration> {
479    let mut findings = Vec::new();
480
481    for phase in phases {
482        for (entry_index, entry) in phase.rules.iter().enumerate() {
483            for reference in entry.before.iter().chain(&entry.after) {
484                let registered_here = phase.rules.iter().enumerate().any(|(other_index, other)| {
485                    other_index != entry_index && other.identity == reference.identity
486                });
487
488                if registered_here {
489                    continue;
490                }
491
492                let registered_elsewhere = phases
493                    .iter()
494                    .find(|other| {
495                        other.id != phase.id
496                            && other
497                                .rules
498                                .iter()
499                                .any(|candidate| candidate.identity == reference.identity)
500                    })
501                    .map(|other| other.id.clone());
502
503                findings.push(Misconfiguration::UnknownRuleReference {
504                    phase: phase.id.clone(),
505                    rule: entry.name,
506                    reference: reference.name,
507                    registered_elsewhere,
508                });
509            }
510        }
511    }
512
513    findings
514}
515
516fn resolve_exclusions(
517    exclusions: Vec<RuleIdentity>,
518    phases: &[Phase],
519) -> (GrHashSet<TypeId>, Vec<Misconfiguration>) {
520    let mut excluded = GrHashSet::default();
521    let mut findings = Vec::new();
522
523    for exclusion in exclusions {
524        if !excluded.insert(exclusion.identity) {
525            continue;
526        }
527
528        let matches: Vec<_> = phases
529            .iter()
530            .flat_map(|phase| &phase.rules)
531            .filter(|entry| entry.identity == exclusion.identity)
532            .collect();
533
534        if matches.is_empty() {
535            findings.push(Misconfiguration::UnknownExclusion(exclusion.name));
536        }
537
538        if matches.iter().any(|entry| !entry.excludable) {
539            findings.push(Misconfiguration::NonExcludable(exclusion.name));
540        }
541    }
542
543    (excluded, findings)
544}
545
546fn compile_phases(
547    phases: Vec<Phase>,
548    order: Option<Vec<usize>>,
549    excluded: &GrHashSet<TypeId>,
550) -> (Vec<CompiledPhase>, Vec<Misconfiguration>) {
551    let execution_order = order.unwrap_or_else(|| (0..phases.len()).collect());
552    let mut slots: Vec<_> = phases.into_iter().map(Some).collect();
553
554    let (compiled_phases, findings): (Vec<_>, Vec<_>) = execution_order
555        .into_iter()
556        .map(|index| {
557            let phase = slots[index]
558                .take()
559                .expect("Each phase must appear exactly once in the execution order");
560
561            compile_phase(phase, excluded)
562        })
563        .unzip();
564
565    (compiled_phases, findings.into_iter().flatten().collect())
566}
567
568fn compile_phase(
569    phase: Phase,
570    excluded: &GrHashSet<TypeId>,
571) -> (CompiledPhase, Vec<Misconfiguration>) {
572    let Phase {
573        id,
574        direction: phase_direction,
575        policy,
576        run_if,
577        rules,
578        ..
579    } = phase;
580
581    let mut by_direction: GrHashMap<_, GrHashMap<TypeId, Vec<_>>> = GrHashMap::default();
582
583    for entry in rules {
584        if entry.excludable && excluded.contains(&entry.identity) {
585            continue;
586        }
587
588        let direction = entry.direction.unwrap_or(phase_direction);
589        by_direction
590            .entry(direction)
591            .or_default()
592            .entry(entry.operand_type)
593            .or_default()
594            .push(entry);
595    }
596
597    let mut passes = Vec::new();
598    let mut findings = Vec::new();
599
600    for direction in DIRECTION_ORDER {
601        let Some(direction_buckets) = by_direction.remove(&direction) else {
602            continue;
603        };
604
605        let mut buckets = GrHashMap::default();
606
607        for (operand_type, entries) in direction_buckets {
608            match order_bucket(&id, entries) {
609                Ok(ordered) => {
610                    buckets.insert(operand_type, ordered);
611                }
612                Err(cycle_finding) => findings.push(cycle_finding),
613            }
614        }
615
616        let has_run_conditions = buckets.values().flatten().any(|rule| rule.run_if.is_some());
617
618        passes.push(CompiledPass {
619            direction,
620            buckets,
621            has_run_conditions,
622        });
623    }
624
625    (
626        CompiledPhase {
627            id,
628            policy,
629            run_if,
630            passes,
631        },
632        findings,
633    )
634}
635
636fn order_bucket(
637    phase_id: &PhaseId,
638    entries: Vec<RuleEntry>,
639) -> Result<Vec<CompiledRule>, Misconfiguration> {
640    let references: Vec<_> = entries.iter().collect();
641
642    let order = match rule_order(&references) {
643        Ok(order) => order,
644        Err(cycle) => {
645            return Err(Misconfiguration::RuleCycle {
646                phase: phase_id.clone(),
647                rules: cycle.iter().map(|&index| references[index].name).collect(),
648            });
649        }
650    };
651
652    let mut slots: Vec<_> = entries.into_iter().map(Some).collect();
653
654    Ok(order
655        .into_iter()
656        .map(|index| {
657            let entry = slots[index]
658                .take()
659                .expect("Each rule must appear exactly once in the bucket order");
660
661            CompiledRule {
662                run_if: entry.run_if,
663                rule: entry.rule,
664            }
665        })
666        .collect())
667}
668
669pub struct Optimizer {
670    phases: Vec<CompiledPhase>,
671}
672
673impl Optimizer {
674    #[must_use]
675    pub const fn builder() -> OptimizerBuilder {
676        OptimizerBuilder::new()
677    }
678
679    #[must_use]
680    pub const fn none() -> Self {
681        Self { phases: Vec::new() }
682    }
683
684    #[must_use]
685    pub const fn is_empty(&self) -> bool {
686        self.phases.is_empty()
687    }
688
689    pub fn run<'a, O: Operand + Clone + 'static>(&'a self, stats: &'a Stats<'a>, root: &O) -> O {
690        self.run_reported(stats, root).0
691    }
692
693    pub fn run_reported<'a, O: Operand + Clone + 'static>(
694        &'a self,
695        stats: &'a Stats<'a>,
696        root: &O,
697    ) -> (O, OptimizationReport) {
698        let mut current = root.clone();
699        let mut phases = Vec::with_capacity(self.phases.len());
700
701        for phase in &self.phases {
702            let (next, stop) = Self::run_phase(phase, stats, current);
703
704            current = next;
705
706            phases.push(PhaseOutcome {
707                label: phase.id.clone(),
708                stop,
709            });
710        }
711
712        (current, OptimizationReport { phases })
713    }
714
715    fn run_phase<O: Operand + Clone + 'static>(
716        phase: &CompiledPhase,
717        stats: &Stats,
718        current: O,
719    ) -> (O, StopReason) {
720        if phase
721            .run_if
722            .as_ref()
723            .is_some_and(|condition| !condition(stats))
724        {
725            return (current, StopReason::Skipped);
726        }
727
728        if phase.passes.is_empty() {
729            return (current, StopReason::Empty);
730        }
731
732        let enabled = enabled_rules(&phase.passes, stats);
733
734        if let Some(passes_enabled) = &enabled {
735            let any_enabled = passes_enabled.iter().any(|pass_enabled| {
736                pass_enabled.as_ref().is_none_or(|buckets_enabled| {
737                    buckets_enabled.values().flatten().any(|&enabled| enabled)
738                })
739            });
740
741            if !any_enabled {
742                return (current, StopReason::Empty);
743            }
744        }
745
746        match phase.policy {
747            FixpointPolicy::Once => {
748                let current = apply_passes(&phase.passes, enabled.as_deref(), current, stats).0;
749                (current, StopReason::CompletedOnce)
750            }
751            FixpointPolicy::Fixpoint { max_iterations } => {
752                let mut current = current;
753                let mut seen: GrHashMap<_, Vec<O>> = GrHashMap::default();
754
755                seen.entry(signature(&current))
756                    .or_default()
757                    .push(current.clone());
758
759                for iteration in 1..=max_iterations {
760                    let (next, changed) =
761                        apply_passes(&phase.passes, enabled.as_deref(), current, stats);
762                    current = next;
763
764                    if !changed {
765                        return (
766                            current,
767                            StopReason::Converged {
768                                iterations: iteration,
769                            },
770                        );
771                    }
772
773                    let bucket = seen.entry(signature(&current)).or_default();
774                    if bucket
775                        .iter()
776                        .any(|plan| current.as_plan_node().dyn_eq(plan.as_plan_node()))
777                    {
778                        return (
779                            current,
780                            StopReason::Oscillation {
781                                iterations: iteration,
782                            },
783                        );
784                    }
785
786                    bucket.push(current.clone());
787                }
788
789                (
790                    current,
791                    StopReason::IterationLimit {
792                        iterations: max_iterations,
793                    },
794                )
795            }
796        }
797    }
798}
799
800type EnabledBuckets = GrHashMap<TypeId, Vec<bool>>;
801type EnabledPasses = Vec<Option<EnabledBuckets>>;
802
803fn enabled_rules(passes: &[CompiledPass], stats: &Stats) -> Option<EnabledPasses> {
804    if passes.iter().all(|pass| !pass.has_run_conditions) {
805        return None;
806    }
807
808    Some(
809        passes
810            .iter()
811            .map(|pass| {
812                if !pass.has_run_conditions {
813                    return None;
814                }
815
816                Some(
817                    pass.buckets
818                        .iter()
819                        .map(|(&operand_type, rules)| {
820                            (
821                                operand_type,
822                                rules
823                                    .iter()
824                                    .map(|rule| {
825                                        rule.run_if
826                                            .as_ref()
827                                            .is_none_or(|condition| condition(stats))
828                                    })
829                                    .collect(),
830                            )
831                        })
832                        .collect(),
833                )
834            })
835            .collect(),
836    )
837}
838
839fn apply_passes<O: Operand + Clone + 'static>(
840    passes: &[CompiledPass],
841    enabled: Option<&[Option<EnabledBuckets>]>,
842    mut current: O,
843    stats: &Stats,
844) -> (O, bool) {
845    let mut changed = false;
846
847    for (pass_index, pass) in passes.iter().enumerate() {
848        let session = Session {
849            buckets: &pass.buckets,
850            enabled: enabled.and_then(|passes_enabled| passes_enabled[pass_index].as_ref()),
851            direction: pass.direction,
852            stats,
853        };
854        let transformed = session.optimize(&current);
855        let (value, was_changed) = transformed.into_parts();
856
857        current = value;
858        changed |= was_changed;
859    }
860
861    (current, changed)
862}
863
864pub struct Session<'a> {
865    buckets: &'a GrHashMap<TypeId, Vec<CompiledRule>>,
866    enabled: Option<&'a EnabledBuckets>,
867    direction: Direction,
868    stats: &'a Stats<'a>,
869}
870
871impl Session<'_> {
872    #[must_use]
873    pub const fn stats(&self) -> &Stats<'_> {
874        self.stats
875    }
876
877    pub fn optimize<O: Operand + Clone + 'static>(&self, operand: &O) -> Transformed<O> {
878        match self.direction {
879            Direction::BottomUp => {
880                let (rebuilt, rebuilt_changed) =
881                    operand.context().optimize(operand, self).into_parts();
882                let (applied, applied_changed) = self.apply_rules(rebuilt).into_parts();
883
884                if rebuilt_changed || applied_changed {
885                    Transformed::changed(applied)
886                } else {
887                    Transformed::unchanged(applied)
888                }
889            }
890            Direction::TopDown => {
891                let (rewritten, rewritten_changed) = self.apply_rules(operand.clone()).into_parts();
892                let (rebuilt, rebuilt_changed) =
893                    rewritten.context().optimize(&rewritten, self).into_parts();
894
895                if rewritten_changed || rebuilt_changed {
896                    Transformed::changed(rebuilt)
897                } else {
898                    Transformed::unchanged(rebuilt)
899                }
900            }
901            Direction::Manual => self.apply_rules(operand.clone()),
902        }
903    }
904
905    fn apply_rules<O: Operand + 'static>(&self, mut operand: O) -> Transformed<O> {
906        let Some(rules) = self.buckets.get(&TypeId::of::<O>()) else {
907            return Transformed::unchanged(operand);
908        };
909
910        let bucket_enabled = self
911            .enabled
912            .and_then(|buckets_enabled| buckets_enabled.get(&TypeId::of::<O>()));
913
914        let mut changed = false;
915
916        for (index, compiled) in rules.iter().enumerate() {
917            if bucket_enabled.is_some_and(|enabled| !enabled[index]) {
918                continue;
919            }
920
921            let rule = compiled
922                .rule
923                .downcast_ref::<ErasedRule<O>>()
924                .expect("Compiled rule must hold an erased rule matching its operand bucket");
925            let (value, was_changed) = rule(operand, self).into_parts();
926
927            operand = value;
928            changed |= was_changed;
929        }
930
931        if changed {
932            Transformed::changed(operand)
933        } else {
934            Transformed::unchanged(operand)
935        }
936    }
937}
938
939fn signature<O: Operand>(operand: &O) -> u64 {
940    let mut hasher = DefaultHasher::new();
941    operand.as_plan_node().dyn_hash(&mut hasher);
942    hasher.finish()
943}
944
945fn rule_order(entries: &[&RuleEntry]) -> Result<Vec<usize>, Vec<usize>> {
946    let mut edges = Vec::new();
947
948    for (index, entry) in entries.iter().enumerate() {
949        for reference in &entry.before {
950            for (other, candidate) in entries.iter().enumerate() {
951                if other != index && candidate.identity == reference.identity {
952                    edges.push((index, other));
953                }
954            }
955        }
956
957        for reference in &entry.after {
958            for (other, candidate) in entries.iter().enumerate() {
959                if other != index && candidate.identity == reference.identity {
960                    edges.push((other, index));
961                }
962            }
963        }
964    }
965
966    toposort(entries.len(), &edges)
967}
968
969fn toposort(count: usize, edges: &[(usize, usize)]) -> Result<Vec<usize>, Vec<usize>> {
970    let mut indegree = vec![0usize; count];
971    let mut adjacency = vec![Vec::new(); count];
972
973    for &(before, after) in edges {
974        adjacency[before].push(after);
975        indegree[after] += 1;
976    }
977
978    let mut ready: Vec<_> = (0..count).filter(|&index| indegree[index] == 0).collect();
979    let mut order = Vec::with_capacity(count);
980
981    while !ready.is_empty() {
982        let mut position = 0;
983        for candidate in 1..ready.len() {
984            if ready[candidate] < ready[position] {
985                position = candidate;
986            }
987        }
988
989        let index = ready.swap_remove(position);
990        order.push(index);
991
992        for &next in &adjacency[index] {
993            indegree[next] -= 1;
994            if indegree[next] == 0 {
995                ready.push(next);
996            }
997        }
998    }
999
1000    if order.len() == count {
1001        Ok(order)
1002    } else {
1003        Err((0..count)
1004            .filter(|&index| !order.contains(&index) && cycles_back(index, &adjacency))
1005            .collect())
1006    }
1007}
1008
1009fn cycles_back(start: usize, adjacency: &[Vec<usize>]) -> bool {
1010    let mut visited = vec![false; adjacency.len()];
1011    let mut stack = adjacency[start].clone();
1012
1013    while let Some(node) = stack.pop() {
1014        if node == start {
1015            return true;
1016        }
1017
1018        if !visited[node] {
1019            visited[node] = true;
1020            stack.extend(adjacency[node].iter().copied());
1021        }
1022    }
1023
1024    false
1025}