Skip to main content

graphrecords_query/optimizer/
plan.rs

1use super::{engine::Session, rule::Transformed};
2use crate::Operand;
3pub use graphrecords_macros::{
4    MatchInputs, OperationInputs, OptimizePlan, OptimizerHints, PlanIdentity, PlanInputs, PlanNode,
5};
6use std::{
7    any::Any,
8    hash::{Hash, Hasher},
9};
10
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
12pub enum EmptyRule {
13    Never,
14    IfAnyInput,
15    IfAllInputs,
16}
17
18pub trait OptimizerHints {
19    fn commutes_with_filter(&self) -> bool {
20        false
21    }
22
23    fn allows_limit_pushdown(&self) -> bool {
24        false
25    }
26
27    fn is_volatile(&self) -> bool {
28        false
29    }
30
31    fn empty_rule(&self) -> EmptyRule {
32        EmptyRule::Never
33    }
34}
35
36#[diagnostic::on_unimplemented(
37    message = "`{Self}` is not a plan node",
38    note = "implement `PlanNode` for `{Self}` or derive it with `#[derive(PlanNode)]`"
39)]
40pub trait PlanNode: Any + OptimizerHints {
41    fn inputs(&self) -> Vec<&dyn PlanNode> {
42        Vec::new()
43    }
44
45    fn contains_volatile(&self) -> bool {
46        self.is_volatile() || self.inputs().into_iter().any(PlanNode::contains_volatile)
47    }
48
49    #[allow(unused_variables)]
50    fn dyn_eq(&self, other: &dyn PlanNode) -> bool {
51        false
52    }
53
54    fn dyn_hash(&self, mut state: &mut dyn Hasher) {
55        self.type_id().hash(&mut state);
56    }
57}
58
59impl dyn PlanNode {
60    pub fn downcast<T: PlanNode>(&self) -> Option<&T> {
61        (self as &dyn Any).downcast_ref()
62    }
63}
64
65#[diagnostic::on_unimplemented(
66    message = "`{Self}` does not expose its inputs",
67    note = "implement `MatchInputs` for `{Self}` or derive it with `#[derive(MatchInputs)]`"
68)]
69pub trait MatchInputs {
70    type Inputs<'a>
71    where
72        Self: 'a;
73
74    fn inputs(&self) -> Self::Inputs<'_>;
75}
76
77#[diagnostic::on_unimplemented(
78    message = "`{Self}` cannot produce its operand",
79    note = "implement `OptimizePlan` for `{Self}` or derive it with `#[derive(OptimizePlan)]`",
80    note = "the derive requires a `#[plan(operand = ...)]` attribute"
81)]
82pub trait OptimizePlan {
83    type Output: Operand;
84
85    fn optimize(&self, original: &Self::Output, session: &Session) -> Transformed<Self::Output>;
86}
87
88pub trait PlanIdentity {
89    fn identity_eq(&self, other: &Self) -> bool;
90
91    fn identity_hash<H: Hasher>(&self, state: &mut H);
92}
93
94impl<T: Operand> PlanIdentity for T {
95    fn identity_eq(&self, other: &Self) -> bool {
96        self.as_plan_node().dyn_eq(other.as_plan_node())
97    }
98
99    fn identity_hash<H: Hasher>(&self, state: &mut H) {
100        self.as_plan_node().dyn_hash(state);
101    }
102}
103
104pub trait PlanInputs: Clone {
105    fn inputs(&self) -> Vec<&dyn PlanNode> {
106        Vec::new()
107    }
108
109    #[allow(unused_variables)]
110    fn optimize(&self, session: &Session) -> Transformed<Self> {
111        Transformed::unchanged(self.clone())
112    }
113}
114
115impl<T: Operand> PlanInputs for T {
116    fn inputs(&self) -> Vec<&dyn PlanNode> {
117        vec![self.as_plan_node()]
118    }
119
120    fn optimize(&self, session: &Session) -> Transformed<Self> {
121        session.optimize(self)
122    }
123}
124
125pub trait OperationInputs: 'static + PlanIdentity + PlanInputs + OptimizerHints {
126    type Inputs<'a, I: 'a>;
127
128    fn inputs<'a, I: 'a>(&'a self, primary: &'a I) -> Self::Inputs<'a, I>;
129}