Skip to main content

uni_plugin/traits/
operator.rs

1//! Optimizer-rule plugins.
2
3use std::sync::Arc;
4
5use datafusion::optimizer::OptimizerRule;
6use datafusion::physical_optimizer::PhysicalOptimizerRule;
7
8/// Phase at which an `OptimizerRule` runs.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum OptimizerPhase {
12    /// Logical optimizer.
13    Logical,
14    /// Physical optimizer.
15    Physical,
16    /// Both — the rule is applied at logical and physical phases.
17    Both,
18}
19
20/// A registered optimizer-rule provider.
21///
22/// A provider that runs at the logical phase returns a logical
23/// [`OptimizerRule`] from [`rule`](Self::rule); a provider that runs at
24/// the physical phase returns a [`PhysicalOptimizerRule`] from
25/// [`physical_rule`](Self::physical_rule). A `Both` provider must
26/// supply both. The host iterates the registered providers, inspects
27/// `phase`, and installs each rule into the matching DataFusion
28/// optimizer chain.
29///
30/// The default `physical_rule` returns `None`, so existing
31/// logical-only providers compile unchanged across the 1.6 → 1.7
32/// minor bump.
33pub trait OptimizerRuleProvider: Send + Sync {
34    /// The DataFusion logical `OptimizerRule` to apply.
35    ///
36    /// Logical-phase and `Both`-phase providers must return a real
37    /// rule. Physical-only providers may return any rule (the host
38    /// ignores it when `phase()` is [`OptimizerPhase::Physical`]);
39    /// returning a sentinel/no-op is conventional. The default impl
40    /// returns a no-op rule that never rewrites.
41    fn rule(&self) -> Arc<dyn OptimizerRule + Send + Sync> {
42        Arc::new(NoopOptimizerRule)
43    }
44
45    /// The DataFusion physical [`PhysicalOptimizerRule`] to apply.
46    ///
47    /// Physical-phase and `Both`-phase providers should return
48    /// `Some(...)`. The default `None` keeps existing logical-only
49    /// providers source-compatible.
50    fn physical_rule(&self) -> Option<Arc<dyn PhysicalOptimizerRule + Send + Sync>> {
51        None
52    }
53
54    /// Phase the rule runs at.
55    fn phase(&self) -> OptimizerPhase;
56
57    /// Ordering hint — lower precedence rules run first.
58    fn precedence(&self) -> i32 {
59        0
60    }
61}
62
63/// No-op logical `OptimizerRule` used as the default for
64/// [`OptimizerRuleProvider::rule`].
65///
66/// Returned by the trait's default `rule()` implementation so that
67/// physical-only providers do not have to construct a sentinel
68/// themselves. The rule is `Bottom-Up` and never transforms the plan.
69#[derive(Debug, Default)]
70pub struct NoopOptimizerRule;
71
72impl OptimizerRule for NoopOptimizerRule {
73    fn name(&self) -> &str {
74        "uni_noop_optimizer_rule"
75    }
76
77    fn apply_order(&self) -> Option<datafusion::optimizer::ApplyOrder> {
78        Some(datafusion::optimizer::ApplyOrder::BottomUp)
79    }
80
81    fn rewrite(
82        &self,
83        plan: datafusion::logical_expr::LogicalPlan,
84        _config: &dyn datafusion::optimizer::OptimizerConfig,
85    ) -> Result<
86        datafusion::common::tree_node::Transformed<datafusion::logical_expr::LogicalPlan>,
87        datafusion::error::DataFusionError,
88    > {
89        Ok(datafusion::common::tree_node::Transformed::no(plan))
90    }
91}