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}