1use std::collections::BTreeMap;
18use std::str::FromStr;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct PhysicalOperatorId(usize);
27
28impl PhysicalOperatorId {
29 pub const fn from_ordinal(ordinal: usize) -> Self {
31 Self(ordinal)
32 }
33
34 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
495pub 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}