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