Skip to main content

hawdb_optimizer_graph/
logical.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Logical optimizer operator metadata.
16
17use std::str::FromStr;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum LogicalPlanKind {
21    CreateSchema,
22    AlterSchema,
23    CreateIndex,
24    CreateConstraint,
25    CreateNode,
26    MergeNode,
27    MergeRelationship,
28    SetProperty,
29    Delete,
30    NodeScan,
31    NodeSeek,
32    RelationshipExpand,
33    OptionalRelationshipExpand,
34    ShortestPath,
35    Filter,
36    Project,
37    Aggregate,
38    Distinct,
39    Sort,
40    Limit,
41    ProcedureCall,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum LogicalPlanClass {
46    Schema,
47    Mutation,
48    Access,
49    Traversal,
50    Relational,
51    Procedure,
52}
53
54pub trait LogicalPlanNode {
55    fn logical_kind(&self) -> LogicalPlanKind;
56
57    fn logical_class(&self) -> LogicalPlanClass {
58        self.logical_kind().class()
59    }
60}
61
62impl LogicalPlanKind {
63    pub fn all() -> &'static [Self] {
64        const ALL: &[LogicalPlanKind] = &[
65            LogicalPlanKind::CreateSchema,
66            LogicalPlanKind::AlterSchema,
67            LogicalPlanKind::CreateIndex,
68            LogicalPlanKind::CreateConstraint,
69            LogicalPlanKind::CreateNode,
70            LogicalPlanKind::MergeNode,
71            LogicalPlanKind::MergeRelationship,
72            LogicalPlanKind::SetProperty,
73            LogicalPlanKind::Delete,
74            LogicalPlanKind::NodeScan,
75            LogicalPlanKind::NodeSeek,
76            LogicalPlanKind::RelationshipExpand,
77            LogicalPlanKind::OptionalRelationshipExpand,
78            LogicalPlanKind::ShortestPath,
79            LogicalPlanKind::Filter,
80            LogicalPlanKind::Project,
81            LogicalPlanKind::Aggregate,
82            LogicalPlanKind::Distinct,
83            LogicalPlanKind::Sort,
84            LogicalPlanKind::Limit,
85            LogicalPlanKind::ProcedureCall,
86        ];
87        ALL
88    }
89
90    pub fn as_str(self) -> &'static str {
91        match self {
92            LogicalPlanKind::CreateSchema => "CreateSchema",
93            LogicalPlanKind::AlterSchema => "AlterSchema",
94            LogicalPlanKind::CreateIndex => "CreateIndex",
95            LogicalPlanKind::CreateConstraint => "CreateConstraint",
96            LogicalPlanKind::CreateNode => "CreateNode",
97            LogicalPlanKind::MergeNode => "MergeNode",
98            LogicalPlanKind::MergeRelationship => "MergeRelationship",
99            LogicalPlanKind::SetProperty => "SetProperty",
100            LogicalPlanKind::Delete => "Delete",
101            LogicalPlanKind::NodeScan => "NodeScan",
102            LogicalPlanKind::NodeSeek => "NodeSeek",
103            LogicalPlanKind::RelationshipExpand => "RelationshipExpand",
104            LogicalPlanKind::OptionalRelationshipExpand => "OptionalRelationshipExpand",
105            LogicalPlanKind::ShortestPath => "ShortestPath",
106            LogicalPlanKind::Filter => "Filter",
107            LogicalPlanKind::Project => "Project",
108            LogicalPlanKind::Aggregate => "Aggregate",
109            LogicalPlanKind::Distinct => "Distinct",
110            LogicalPlanKind::Sort => "Sort",
111            LogicalPlanKind::Limit => "Limit",
112            LogicalPlanKind::ProcedureCall => "ProcedureCall",
113        }
114    }
115
116    pub fn class(self) -> LogicalPlanClass {
117        match self {
118            LogicalPlanKind::CreateSchema
119            | LogicalPlanKind::AlterSchema
120            | LogicalPlanKind::CreateIndex
121            | LogicalPlanKind::CreateConstraint => LogicalPlanClass::Schema,
122            LogicalPlanKind::CreateNode
123            | LogicalPlanKind::MergeNode
124            | LogicalPlanKind::MergeRelationship
125            | LogicalPlanKind::SetProperty
126            | LogicalPlanKind::Delete => LogicalPlanClass::Mutation,
127            LogicalPlanKind::NodeScan | LogicalPlanKind::NodeSeek => LogicalPlanClass::Access,
128            LogicalPlanKind::RelationshipExpand
129            | LogicalPlanKind::OptionalRelationshipExpand
130            | LogicalPlanKind::ShortestPath => LogicalPlanClass::Traversal,
131            LogicalPlanKind::Filter
132            | LogicalPlanKind::Project
133            | LogicalPlanKind::Aggregate
134            | LogicalPlanKind::Distinct
135            | LogicalPlanKind::Sort
136            | LogicalPlanKind::Limit => LogicalPlanClass::Relational,
137            LogicalPlanKind::ProcedureCall => LogicalPlanClass::Procedure,
138        }
139    }
140}
141
142impl LogicalPlanClass {
143    pub fn all() -> &'static [Self] {
144        const ALL: &[LogicalPlanClass] = &[
145            LogicalPlanClass::Schema,
146            LogicalPlanClass::Mutation,
147            LogicalPlanClass::Access,
148            LogicalPlanClass::Traversal,
149            LogicalPlanClass::Relational,
150            LogicalPlanClass::Procedure,
151        ];
152        ALL
153    }
154
155    pub fn as_str(self) -> &'static str {
156        match self {
157            LogicalPlanClass::Schema => "schema",
158            LogicalPlanClass::Mutation => "mutation",
159            LogicalPlanClass::Access => "access",
160            LogicalPlanClass::Traversal => "traversal",
161            LogicalPlanClass::Relational => "relational",
162            LogicalPlanClass::Procedure => "procedure",
163        }
164    }
165}
166
167impl FromStr for LogicalPlanKind {
168    type Err = &'static str;
169
170    fn from_str(value: &str) -> Result<Self, Self::Err> {
171        Self::all()
172            .iter()
173            .copied()
174            .find(|kind| kind.as_str() == value)
175            .ok_or("unknown logical plan kind")
176    }
177}
178
179impl FromStr for LogicalPlanClass {
180    type Err = &'static str;
181
182    fn from_str(value: &str) -> Result<Self, Self::Err> {
183        Self::all()
184            .iter()
185            .copied()
186            .find(|class| class.as_str() == value)
187            .ok_or("unknown logical plan class")
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::{LogicalPlanClass, LogicalPlanKind, LogicalPlanNode};
194
195    #[test]
196    fn logical_plan_kind_exposes_stable_strings_and_classes() {
197        assert_eq!(LogicalPlanKind::NodeSeek.as_str(), "NodeSeek");
198        assert_eq!(LogicalPlanKind::NodeSeek.class(), LogicalPlanClass::Access);
199        assert_eq!(
200            LogicalPlanKind::RelationshipExpand.class(),
201            LogicalPlanClass::Traversal
202        );
203        assert_eq!(
204            LogicalPlanKind::Project.class(),
205            LogicalPlanClass::Relational
206        );
207    }
208
209    #[test]
210    fn logical_plan_kind_strings_round_trip_for_diagnostics() {
211        for kind in LogicalPlanKind::all() {
212            assert_eq!(kind.as_str().parse::<LogicalPlanKind>(), Ok(*kind));
213        }
214        assert!("UnknownLogical".parse::<LogicalPlanKind>().is_err());
215    }
216
217    #[test]
218    fn logical_plan_class_strings_round_trip_for_diagnostics() {
219        for class in LogicalPlanClass::all() {
220            assert_eq!(class.as_str().parse::<LogicalPlanClass>(), Ok(*class));
221        }
222        assert!("unknown".parse::<LogicalPlanClass>().is_err());
223    }
224
225    struct TestLogicalNode;
226
227    impl LogicalPlanNode for TestLogicalNode {
228        fn logical_kind(&self) -> LogicalPlanKind {
229            LogicalPlanKind::Filter
230        }
231    }
232
233    #[test]
234    fn logical_plan_node_derives_class_from_kind() {
235        let node = TestLogicalNode;
236        assert_eq!(node.logical_class(), LogicalPlanClass::Relational);
237    }
238}