Skip to main content

hawdb_plan_cypher/physical/
metadata.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//! Physical optimizer operator metadata.
16
17use std::collections::BTreeMap;
18use std::str::FromStr;
19
20/// A plan-local physical operator identity assigned in pre-order.
21///
22/// The identity is stable for one physical plan shape and lets planning and
23/// execution diagnostics correlate repeated operators without relying on
24/// operator names or process-local addresses.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct PhysicalOperatorId(usize);
27
28impl PhysicalOperatorId {
29    /// Creates an identity from its pre-order position in the physical plan.
30    pub const fn from_ordinal(ordinal: usize) -> Self {
31        Self(ordinal)
32    }
33
34    /// Returns the operator's zero-based pre-order position.
35    pub const fn ordinal(self) -> usize {
36        self.0
37    }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum PhysicalPlanKind {
42    CreateNodeLabel,
43    CreateRelationshipType,
44    CreateNodeTable,
45    CreateRelationshipTable,
46    CreateProperty,
47    AlterTableState,
48    AlterPropertyState,
49    CreateIndex,
50    CreateCompositeIndex,
51    CreateRangeIndex,
52    CreateFullTextIndex,
53    CreateUniqueConstraint,
54    CreateNodePropertyExistsConstraint,
55    CreateRelationshipUniqueConstraint,
56    CreateRelationshipPropertyExistsConstraint,
57    ProjectGraph,
58    GraphAlgorithm,
59    VectorSeedScan,
60    TextSeedScan,
61    GraphSeedScan,
62    VectorIndexScan,
63    CreateNode,
64    UnwindMutationExec,
65    MergeNode,
66    MergeRelationship,
67    MergeMatchedRelationship,
68    MergeRelationshipFromMatchedRelationship,
69    MergeRelationshipToMatchedTarget,
70    MergeRelationshipFromMatchedTarget,
71    CreateMatchedRelationship,
72    SetNodeProperty,
73    SetNodeProperties,
74    SetNodePropertiesReturn,
75    SetRelationshipProperty,
76    SetRelationshipProperties,
77    DeleteNode,
78    DeleteRelationship,
79    DeleteRelationshipTargetNodes,
80    CreateRelationship,
81    GraphMatchExec,
82    EmptyExec,
83    SeqNodeScan,
84    NodeProjectionScanExec,
85    SourceSegmentScan,
86    NodeCartesianProductExec,
87    HashJoinExec,
88    NodeColumnLookupExec,
89    IndexNodeSeek,
90    IndexNodeMultiSeek,
91    IndexNodeUnionSeek,
92    IndexNodeCompositeSeek,
93    IndexNodeCompositeRangeSeek,
94    IndexNodeRangeSeek,
95    IndexNodeTextSeek,
96    AdjacencyExpandExec,
97    AdjacencyExistsExec,
98    OptionalDegreeExec,
99    OptionalRelationshipCountSumExec,
100    NodeCountExec,
101    RelationshipCountExec,
102    ThreadRepairStatsExec,
103    ShortestPathExec,
104    FilterExec,
105    ProjectExec,
106    AggregateExec,
107    DistinctExec,
108    SortExec,
109    TopNExec,
110    LimitExec,
111    ScoringRerankExec,
112    ScoringProgramExec,
113    HostScoringExec,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
117pub enum PhysicalPlanClass {
118    Schema,
119    Mutation,
120    Access,
121    Traversal,
122    Relational,
123    Procedure,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq)]
127pub enum PlanChildren<'a, P> {
128    None,
129    Unary(&'a P),
130    Binary(&'a P, &'a P),
131}
132
133pub trait PhysicalPlanNode {
134    fn kind(&self) -> PhysicalPlanKind;
135
136    fn children(&self) -> PlanChildren<'_, Self>
137    where
138        Self: Sized;
139
140    fn class(&self) -> PhysicalPlanClass {
141        self.kind().class()
142    }
143}
144
145impl PhysicalPlanKind {
146    pub fn all() -> &'static [Self] {
147        const ALL: &[PhysicalPlanKind] = &[
148            PhysicalPlanKind::CreateNodeLabel,
149            PhysicalPlanKind::CreateRelationshipType,
150            PhysicalPlanKind::CreateNodeTable,
151            PhysicalPlanKind::CreateRelationshipTable,
152            PhysicalPlanKind::CreateProperty,
153            PhysicalPlanKind::AlterTableState,
154            PhysicalPlanKind::AlterPropertyState,
155            PhysicalPlanKind::CreateIndex,
156            PhysicalPlanKind::CreateCompositeIndex,
157            PhysicalPlanKind::CreateRangeIndex,
158            PhysicalPlanKind::CreateFullTextIndex,
159            PhysicalPlanKind::CreateUniqueConstraint,
160            PhysicalPlanKind::CreateNodePropertyExistsConstraint,
161            PhysicalPlanKind::CreateRelationshipUniqueConstraint,
162            PhysicalPlanKind::CreateRelationshipPropertyExistsConstraint,
163            PhysicalPlanKind::ProjectGraph,
164            PhysicalPlanKind::GraphAlgorithm,
165            PhysicalPlanKind::VectorSeedScan,
166            PhysicalPlanKind::TextSeedScan,
167            PhysicalPlanKind::GraphSeedScan,
168            PhysicalPlanKind::VectorIndexScan,
169            PhysicalPlanKind::CreateNode,
170            PhysicalPlanKind::UnwindMutationExec,
171            PhysicalPlanKind::MergeNode,
172            PhysicalPlanKind::MergeRelationship,
173            PhysicalPlanKind::MergeMatchedRelationship,
174            PhysicalPlanKind::MergeRelationshipFromMatchedRelationship,
175            PhysicalPlanKind::MergeRelationshipToMatchedTarget,
176            PhysicalPlanKind::MergeRelationshipFromMatchedTarget,
177            PhysicalPlanKind::CreateMatchedRelationship,
178            PhysicalPlanKind::SetNodeProperty,
179            PhysicalPlanKind::SetNodeProperties,
180            PhysicalPlanKind::SetNodePropertiesReturn,
181            PhysicalPlanKind::SetRelationshipProperty,
182            PhysicalPlanKind::SetRelationshipProperties,
183            PhysicalPlanKind::DeleteNode,
184            PhysicalPlanKind::DeleteRelationship,
185            PhysicalPlanKind::DeleteRelationshipTargetNodes,
186            PhysicalPlanKind::CreateRelationship,
187            PhysicalPlanKind::GraphMatchExec,
188            PhysicalPlanKind::EmptyExec,
189            PhysicalPlanKind::SeqNodeScan,
190            PhysicalPlanKind::NodeProjectionScanExec,
191            PhysicalPlanKind::SourceSegmentScan,
192            PhysicalPlanKind::NodeCartesianProductExec,
193            PhysicalPlanKind::HashJoinExec,
194            PhysicalPlanKind::NodeColumnLookupExec,
195            PhysicalPlanKind::IndexNodeSeek,
196            PhysicalPlanKind::IndexNodeMultiSeek,
197            PhysicalPlanKind::IndexNodeUnionSeek,
198            PhysicalPlanKind::IndexNodeCompositeSeek,
199            PhysicalPlanKind::IndexNodeCompositeRangeSeek,
200            PhysicalPlanKind::IndexNodeRangeSeek,
201            PhysicalPlanKind::IndexNodeTextSeek,
202            PhysicalPlanKind::AdjacencyExpandExec,
203            PhysicalPlanKind::AdjacencyExistsExec,
204            PhysicalPlanKind::OptionalDegreeExec,
205            PhysicalPlanKind::OptionalRelationshipCountSumExec,
206            PhysicalPlanKind::NodeCountExec,
207            PhysicalPlanKind::RelationshipCountExec,
208            PhysicalPlanKind::ThreadRepairStatsExec,
209            PhysicalPlanKind::ShortestPathExec,
210            PhysicalPlanKind::FilterExec,
211            PhysicalPlanKind::ProjectExec,
212            PhysicalPlanKind::AggregateExec,
213            PhysicalPlanKind::DistinctExec,
214            PhysicalPlanKind::SortExec,
215            PhysicalPlanKind::TopNExec,
216            PhysicalPlanKind::LimitExec,
217            PhysicalPlanKind::ScoringRerankExec,
218            PhysicalPlanKind::ScoringProgramExec,
219            PhysicalPlanKind::HostScoringExec,
220        ];
221        ALL
222    }
223
224    pub fn as_str(self) -> &'static str {
225        match self {
226            PhysicalPlanKind::CreateNodeLabel => "CreateNodeLabel",
227            PhysicalPlanKind::CreateRelationshipType => "CreateRelationshipType",
228            PhysicalPlanKind::CreateNodeTable => "CreateNodeTable",
229            PhysicalPlanKind::CreateRelationshipTable => "CreateRelationshipTable",
230            PhysicalPlanKind::CreateProperty => "CreateProperty",
231            PhysicalPlanKind::AlterTableState => "AlterTableState",
232            PhysicalPlanKind::AlterPropertyState => "AlterPropertyState",
233            PhysicalPlanKind::CreateIndex => "CreateIndex",
234            PhysicalPlanKind::CreateCompositeIndex => "CreateCompositeIndex",
235            PhysicalPlanKind::CreateRangeIndex => "CreateRangeIndex",
236            PhysicalPlanKind::CreateFullTextIndex => "CreateFullTextIndex",
237            PhysicalPlanKind::CreateUniqueConstraint => "CreateUniqueConstraint",
238            PhysicalPlanKind::CreateNodePropertyExistsConstraint => {
239                "CreateNodePropertyExistsConstraint"
240            }
241            PhysicalPlanKind::CreateRelationshipUniqueConstraint => {
242                "CreateRelationshipUniqueConstraint"
243            }
244            PhysicalPlanKind::CreateRelationshipPropertyExistsConstraint => {
245                "CreateRelationshipPropertyExistsConstraint"
246            }
247            PhysicalPlanKind::ProjectGraph => "ProjectGraph",
248            PhysicalPlanKind::GraphAlgorithm => "GraphAlgorithm",
249            PhysicalPlanKind::VectorSeedScan => "VectorSeedScan",
250            PhysicalPlanKind::TextSeedScan => "TextSeedScan",
251            PhysicalPlanKind::GraphSeedScan => "GraphSeedScan",
252            PhysicalPlanKind::VectorIndexScan => "VectorIndexScan",
253            PhysicalPlanKind::CreateNode => "CreateNode",
254            PhysicalPlanKind::UnwindMutationExec => "UnwindMutationExec",
255            PhysicalPlanKind::MergeNode => "MergeNode",
256            PhysicalPlanKind::MergeRelationship => "MergeRelationship",
257            PhysicalPlanKind::MergeMatchedRelationship => "MergeMatchedRelationship",
258            PhysicalPlanKind::MergeRelationshipFromMatchedRelationship => {
259                "MergeRelationshipFromMatchedRelationship"
260            }
261            PhysicalPlanKind::MergeRelationshipToMatchedTarget => {
262                "MergeRelationshipToMatchedTarget"
263            }
264            PhysicalPlanKind::MergeRelationshipFromMatchedTarget => {
265                "MergeRelationshipFromMatchedTarget"
266            }
267            PhysicalPlanKind::CreateMatchedRelationship => "CreateMatchedRelationship",
268            PhysicalPlanKind::SetNodeProperty => "SetNodeProperty",
269            PhysicalPlanKind::SetNodeProperties => "SetNodeProperties",
270            PhysicalPlanKind::SetNodePropertiesReturn => "SetNodePropertiesReturn",
271            PhysicalPlanKind::SetRelationshipProperty => "SetRelationshipProperty",
272            PhysicalPlanKind::SetRelationshipProperties => "SetRelationshipProperties",
273            PhysicalPlanKind::DeleteNode => "DeleteNode",
274            PhysicalPlanKind::DeleteRelationship => "DeleteRelationship",
275            PhysicalPlanKind::DeleteRelationshipTargetNodes => "DeleteRelationshipTargetNodes",
276            PhysicalPlanKind::CreateRelationship => "CreateRelationship",
277            PhysicalPlanKind::GraphMatchExec => "GraphMatchExec",
278            PhysicalPlanKind::EmptyExec => "EmptyExec",
279            PhysicalPlanKind::SeqNodeScan => "SeqNodeScan",
280            PhysicalPlanKind::NodeProjectionScanExec => "NodeProjectionScanExec",
281            PhysicalPlanKind::SourceSegmentScan => "SourceSegmentScan",
282            PhysicalPlanKind::HashJoinExec => "HashJoinExec",
283            PhysicalPlanKind::NodeCartesianProductExec => "NodeCartesianProductExec",
284            PhysicalPlanKind::NodeColumnLookupExec => "NodeColumnLookupExec",
285            PhysicalPlanKind::IndexNodeSeek => "IndexNodeSeek",
286            PhysicalPlanKind::IndexNodeMultiSeek => "IndexNodeMultiSeek",
287            PhysicalPlanKind::IndexNodeUnionSeek => "IndexNodeUnionSeek",
288            PhysicalPlanKind::IndexNodeCompositeSeek => "IndexNodeCompositeSeek",
289            PhysicalPlanKind::IndexNodeCompositeRangeSeek => "IndexNodeCompositeRangeSeek",
290            PhysicalPlanKind::IndexNodeRangeSeek => "IndexNodeRangeSeek",
291            PhysicalPlanKind::IndexNodeTextSeek => "IndexNodeTextSeek",
292            PhysicalPlanKind::AdjacencyExpandExec => "AdjacencyExpandExec",
293            PhysicalPlanKind::AdjacencyExistsExec => "AdjacencyExistsExec",
294            PhysicalPlanKind::OptionalDegreeExec => "OptionalDegreeExec",
295            PhysicalPlanKind::OptionalRelationshipCountSumExec => {
296                "OptionalRelationshipCountSumExec"
297            }
298            PhysicalPlanKind::NodeCountExec => "NodeCountExec",
299            PhysicalPlanKind::RelationshipCountExec => "RelationshipCountExec",
300            PhysicalPlanKind::ThreadRepairStatsExec => "ThreadRepairStatsExec",
301            PhysicalPlanKind::ShortestPathExec => "ShortestPathExec",
302            PhysicalPlanKind::FilterExec => "FilterExec",
303            PhysicalPlanKind::ProjectExec => "ProjectExec",
304            PhysicalPlanKind::AggregateExec => "AggregateExec",
305            PhysicalPlanKind::DistinctExec => "DistinctExec",
306            PhysicalPlanKind::SortExec => "SortExec",
307            PhysicalPlanKind::TopNExec => "TopNExec",
308            PhysicalPlanKind::LimitExec => "LimitExec",
309            PhysicalPlanKind::ScoringRerankExec => "ScoringRerankExec",
310            PhysicalPlanKind::ScoringProgramExec => "ScoringProgramExec",
311            PhysicalPlanKind::HostScoringExec => "HostScoringExec",
312        }
313    }
314
315    pub fn class(self) -> PhysicalPlanClass {
316        match self {
317            PhysicalPlanKind::CreateNodeLabel
318            | PhysicalPlanKind::CreateRelationshipType
319            | PhysicalPlanKind::CreateNodeTable
320            | PhysicalPlanKind::CreateRelationshipTable
321            | PhysicalPlanKind::CreateProperty
322            | PhysicalPlanKind::AlterTableState
323            | PhysicalPlanKind::AlterPropertyState
324            | PhysicalPlanKind::CreateIndex
325            | PhysicalPlanKind::CreateCompositeIndex
326            | PhysicalPlanKind::CreateRangeIndex
327            | PhysicalPlanKind::CreateFullTextIndex
328            | PhysicalPlanKind::CreateUniqueConstraint
329            | PhysicalPlanKind::CreateNodePropertyExistsConstraint
330            | PhysicalPlanKind::CreateRelationshipUniqueConstraint
331            | PhysicalPlanKind::CreateRelationshipPropertyExistsConstraint => {
332                PhysicalPlanClass::Schema
333            }
334            PhysicalPlanKind::CreateNode
335            | PhysicalPlanKind::UnwindMutationExec
336            | PhysicalPlanKind::MergeNode
337            | PhysicalPlanKind::MergeRelationship
338            | PhysicalPlanKind::MergeMatchedRelationship
339            | PhysicalPlanKind::MergeRelationshipFromMatchedRelationship
340            | PhysicalPlanKind::MergeRelationshipToMatchedTarget
341            | PhysicalPlanKind::MergeRelationshipFromMatchedTarget
342            | PhysicalPlanKind::CreateMatchedRelationship
343            | PhysicalPlanKind::SetNodeProperty
344            | PhysicalPlanKind::SetNodeProperties
345            | PhysicalPlanKind::SetNodePropertiesReturn
346            | PhysicalPlanKind::SetRelationshipProperty
347            | PhysicalPlanKind::SetRelationshipProperties
348            | PhysicalPlanKind::DeleteNode
349            | PhysicalPlanKind::DeleteRelationship
350            | PhysicalPlanKind::DeleteRelationshipTargetNodes
351            | PhysicalPlanKind::CreateRelationship => PhysicalPlanClass::Mutation,
352            PhysicalPlanKind::SeqNodeScan
353            | PhysicalPlanKind::NodeProjectionScanExec
354            | PhysicalPlanKind::SourceSegmentScan
355            | PhysicalPlanKind::NodeColumnLookupExec
356            | PhysicalPlanKind::IndexNodeSeek
357            | PhysicalPlanKind::IndexNodeMultiSeek
358            | PhysicalPlanKind::IndexNodeUnionSeek
359            | PhysicalPlanKind::IndexNodeCompositeSeek
360            | PhysicalPlanKind::IndexNodeCompositeRangeSeek
361            | PhysicalPlanKind::IndexNodeRangeSeek
362            | PhysicalPlanKind::IndexNodeTextSeek => PhysicalPlanClass::Access,
363            PhysicalPlanKind::GraphMatchExec
364            | PhysicalPlanKind::AdjacencyExpandExec
365            | PhysicalPlanKind::AdjacencyExistsExec
366            | PhysicalPlanKind::OptionalDegreeExec
367            | PhysicalPlanKind::OptionalRelationshipCountSumExec
368            | PhysicalPlanKind::ShortestPathExec => PhysicalPlanClass::Traversal,
369            PhysicalPlanKind::NodeCountExec
370            | PhysicalPlanKind::RelationshipCountExec
371            | PhysicalPlanKind::EmptyExec
372            | PhysicalPlanKind::NodeCartesianProductExec
373            | PhysicalPlanKind::HashJoinExec
374            | PhysicalPlanKind::FilterExec
375            | PhysicalPlanKind::ProjectExec
376            | PhysicalPlanKind::AggregateExec
377            | PhysicalPlanKind::DistinctExec
378            | PhysicalPlanKind::SortExec
379            | PhysicalPlanKind::TopNExec
380            | PhysicalPlanKind::LimitExec
381            | PhysicalPlanKind::ScoringRerankExec
382            | PhysicalPlanKind::ScoringProgramExec
383            | PhysicalPlanKind::HostScoringExec => PhysicalPlanClass::Relational,
384            PhysicalPlanKind::ProjectGraph
385            | PhysicalPlanKind::GraphAlgorithm
386            | PhysicalPlanKind::VectorSeedScan
387            | PhysicalPlanKind::TextSeedScan
388            | PhysicalPlanKind::GraphSeedScan
389            | PhysicalPlanKind::VectorIndexScan
390            | PhysicalPlanKind::ThreadRepairStatsExec => PhysicalPlanClass::Procedure,
391        }
392    }
393}
394
395impl PhysicalPlanClass {
396    pub fn all() -> &'static [Self] {
397        const ALL: &[PhysicalPlanClass] = &[
398            PhysicalPlanClass::Schema,
399            PhysicalPlanClass::Mutation,
400            PhysicalPlanClass::Access,
401            PhysicalPlanClass::Traversal,
402            PhysicalPlanClass::Relational,
403            PhysicalPlanClass::Procedure,
404        ];
405        ALL
406    }
407
408    pub fn as_str(self) -> &'static str {
409        match self {
410            PhysicalPlanClass::Schema => "schema",
411            PhysicalPlanClass::Mutation => "mutation",
412            PhysicalPlanClass::Access => "access",
413            PhysicalPlanClass::Traversal => "traversal",
414            PhysicalPlanClass::Relational => "relational",
415            PhysicalPlanClass::Procedure => "procedure",
416        }
417    }
418}
419
420impl FromStr for PhysicalPlanKind {
421    type Err = &'static str;
422
423    fn from_str(value: &str) -> Result<Self, Self::Err> {
424        Self::all()
425            .iter()
426            .copied()
427            .find(|kind| kind.as_str() == value)
428            .ok_or("unknown physical plan kind")
429    }
430}
431
432impl FromStr for PhysicalPlanClass {
433    type Err = &'static str;
434
435    fn from_str(value: &str) -> Result<Self, Self::Err> {
436        Self::all()
437            .iter()
438            .copied()
439            .find(|class| class.as_str() == value)
440            .ok_or("unknown physical plan class")
441    }
442}
443
444impl<'a, P> PlanChildren<'a, P> {
445    pub fn len(self) -> usize {
446        match self {
447            PlanChildren::None => 0,
448            PlanChildren::Unary(_) => 1,
449            PlanChildren::Binary(_, _) => 2,
450        }
451    }
452
453    pub fn is_empty(self) -> bool {
454        self.len() == 0
455    }
456}
457
458pub fn plan_operator_counts<P>(plan: &P) -> BTreeMap<String, usize>
459where
460    P: PhysicalPlanNode,
461{
462    let mut counts = BTreeMap::new();
463    visit_plan(plan, &mut |node| {
464        *counts.entry(node.kind().as_str().to_string()).or_default() += 1;
465    });
466    counts
467}
468
469pub fn plan_class_counts<P>(plan: &P) -> BTreeMap<String, usize>
470where
471    P: PhysicalPlanNode,
472{
473    let mut counts = BTreeMap::new();
474    visit_plan(plan, &mut |node| {
475        *counts.entry(node.class().as_str().to_string()).or_default() += 1;
476    });
477    counts
478}
479
480pub fn visit_plan<P>(plan: &P, visitor: &mut impl FnMut(&P))
481where
482    P: PhysicalPlanNode,
483{
484    visitor(plan);
485    match plan.children() {
486        PlanChildren::None => {}
487        PlanChildren::Unary(input) => visit_plan(input, visitor),
488        PlanChildren::Binary(left, right) => {
489            visit_plan(left, visitor);
490            visit_plan(right, visitor);
491        }
492    }
493}
494
495/// Visits a physical plan in pre-order and assigns each operator its plan-local identity.
496pub fn visit_plan_with_ids<P>(plan: &P, visitor: &mut impl FnMut(PhysicalOperatorId, &P))
497where
498    P: PhysicalPlanNode,
499{
500    fn visit<P>(
501        plan: &P,
502        next_ordinal: &mut usize,
503        visitor: &mut impl FnMut(PhysicalOperatorId, &P),
504    ) where
505        P: PhysicalPlanNode,
506    {
507        let operator_id = PhysicalOperatorId::from_ordinal(*next_ordinal);
508        *next_ordinal = next_ordinal
509            .checked_add(1)
510            .expect("physical plan operator count exceeds usize");
511        visitor(operator_id, plan);
512        match plan.children() {
513            PlanChildren::None => {}
514            PlanChildren::Unary(input) => visit(input, next_ordinal, visitor),
515            PlanChildren::Binary(left, right) => {
516                visit(left, next_ordinal, visitor);
517                visit(right, next_ordinal, visitor);
518            }
519        }
520    }
521
522    let mut next_ordinal = 0;
523    visit(plan, &mut next_ordinal, visitor);
524}
525
526#[cfg(test)]
527mod tests {
528    use super::{
529        plan_class_counts, plan_operator_counts, visit_plan, visit_plan_with_ids,
530        PhysicalPlanClass, PhysicalPlanKind, PhysicalPlanNode, PlanChildren,
531    };
532
533    #[test]
534    fn physical_plan_kind_exposes_stable_strings_and_classes() {
535        assert_eq!(PhysicalPlanKind::IndexNodeSeek.as_str(), "IndexNodeSeek");
536        assert_eq!(
537            PhysicalPlanKind::IndexNodeSeek.class(),
538            PhysicalPlanClass::Access
539        );
540        assert_eq!(
541            PhysicalPlanKind::AdjacencyExpandExec.class(),
542            PhysicalPlanClass::Traversal
543        );
544        assert_eq!(
545            PhysicalPlanKind::ProjectExec.class(),
546            PhysicalPlanClass::Relational
547        );
548    }
549
550    #[test]
551    fn physical_plan_kind_strings_round_trip_for_diagnostics() {
552        for kind in PhysicalPlanKind::all() {
553            assert_eq!(kind.as_str().parse::<PhysicalPlanKind>(), Ok(*kind));
554        }
555        assert!("UnknownExec".parse::<PhysicalPlanKind>().is_err());
556    }
557
558    #[test]
559    fn physical_plan_class_exposes_stable_strings() {
560        assert_eq!(PhysicalPlanClass::Schema.as_str(), "schema");
561        assert_eq!(PhysicalPlanClass::Mutation.as_str(), "mutation");
562        assert_eq!(PhysicalPlanClass::Access.as_str(), "access");
563        assert_eq!(PhysicalPlanClass::Traversal.as_str(), "traversal");
564        assert_eq!(PhysicalPlanClass::Relational.as_str(), "relational");
565        assert_eq!(PhysicalPlanClass::Procedure.as_str(), "procedure");
566    }
567
568    #[test]
569    fn physical_plan_class_strings_round_trip_for_diagnostics() {
570        for class in PhysicalPlanClass::all() {
571            assert_eq!(class.as_str().parse::<PhysicalPlanClass>(), Ok(*class));
572        }
573        assert!("unknown".parse::<PhysicalPlanClass>().is_err());
574    }
575
576    #[test]
577    fn plan_children_reports_arity_without_knowing_plan_type() {
578        assert_eq!(PlanChildren::<&str>::None.len(), 0);
579        assert!(PlanChildren::<&str>::None.is_empty());
580
581        let child = "scan";
582        assert_eq!(PlanChildren::Unary(&child).len(), 1);
583
584        let left = "left";
585        let right = "right";
586        assert_eq!(PlanChildren::Binary(&left, &right).len(), 2);
587    }
588
589    #[derive(Debug)]
590    enum TestPlan {
591        Seek,
592        Project(Box<TestPlan>),
593        Join(Box<TestPlan>, Box<TestPlan>),
594    }
595
596    impl PhysicalPlanNode for TestPlan {
597        fn kind(&self) -> PhysicalPlanKind {
598            match self {
599                TestPlan::Seek => PhysicalPlanKind::IndexNodeSeek,
600                TestPlan::Project(_) => PhysicalPlanKind::ProjectExec,
601                TestPlan::Join(_, _) => PhysicalPlanKind::NodeCartesianProductExec,
602            }
603        }
604
605        fn children(&self) -> PlanChildren<'_, Self> {
606            match self {
607                TestPlan::Seek => PlanChildren::None,
608                TestPlan::Project(input) => PlanChildren::Unary(input),
609                TestPlan::Join(left, right) => PlanChildren::Binary(left, right),
610            }
611        }
612    }
613
614    #[test]
615    fn plan_node_helpers_traverse_without_knowing_plan_payloads() {
616        let plan = TestPlan::Project(Box::new(TestPlan::Join(
617            Box::new(TestPlan::Seek),
618            Box::new(TestPlan::Seek),
619        )));
620
621        let mut visit_order = Vec::new();
622        visit_plan(&plan, &mut |node| {
623            visit_order.push(node.kind().as_str());
624        });
625
626        assert_eq!(
627            visit_order,
628            vec![
629                "ProjectExec",
630                "NodeCartesianProductExec",
631                "IndexNodeSeek",
632                "IndexNodeSeek"
633            ]
634        );
635        assert_eq!(plan_operator_counts(&plan)["IndexNodeSeek"], 2);
636        assert_eq!(plan_operator_counts(&plan)["ProjectExec"], 1);
637        assert_eq!(plan_class_counts(&plan)["access"], 2);
638        assert_eq!(plan_class_counts(&plan)["relational"], 2);
639
640        let mut identified = Vec::new();
641        visit_plan_with_ids(&plan, &mut |operator_id, node| {
642            identified.push((operator_id.ordinal(), node.kind().as_str()));
643        });
644        assert_eq!(
645            identified,
646            vec![
647                (0, "ProjectExec"),
648                (1, "NodeCartesianProductExec"),
649                (2, "IndexNodeSeek"),
650                (3, "IndexNodeSeek"),
651            ]
652        );
653    }
654}