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