graphrecords_query/optimizer/
plan.rs1use 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}