1use crate::{
7 db::query::{
8 admission::QueryAdmissionSummary,
9 explain::{ExplainAccessPath, ExplainPlan, ExplainPredicate},
10 plan::{AggregateKind, ResidualFilterShape},
11 trace::TraceReuseEvent,
12 },
13 value::Value,
14};
15use std::fmt::{self, Debug};
16
17#[cfg_attr(
18 doc,
19 doc = "ExplainPropertyMap\n\nStable ordered property map for EXPLAIN metadata.\nKeeps deterministic key order without `BTreeMap`."
20)]
21#[derive(Clone, Default, Eq, PartialEq)]
22pub struct ExplainPropertyMap {
23 entries: Vec<(&'static str, Value)>,
24}
25
26impl ExplainPropertyMap {
27 #[must_use]
29 pub const fn new() -> Self {
30 Self {
31 entries: Vec::new(),
32 }
33 }
34
35 pub fn insert(&mut self, key: &'static str, value: Value) -> Option<Value> {
37 match self
38 .entries
39 .binary_search_by_key(&key, |(existing_key, _)| *existing_key)
40 {
41 Ok(index) => Some(std::mem::replace(&mut self.entries[index].1, value)),
42 Err(index) => {
43 self.entries.insert(index, (key, value));
44 None
45 }
46 }
47 }
48
49 #[must_use]
51 pub fn get(&self, key: &str) -> Option<&Value> {
52 self.entries
53 .binary_search_by_key(&key, |(existing_key, _)| *existing_key)
54 .ok()
55 .map(|index| &self.entries[index].1)
56 }
57
58 #[must_use]
60 #[cfg(test)]
61 pub fn contains_key(&self, key: &str) -> bool {
62 self.get(key).is_some()
63 }
64
65 #[must_use]
67 pub const fn is_empty(&self) -> bool {
68 self.entries.is_empty()
69 }
70
71 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Value)> {
73 self.entries.iter().map(|(key, value)| (*key, value))
74 }
75}
76
77impl Debug for ExplainPropertyMap {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 let mut map = f.debug_map();
80 for (key, value) in self.iter() {
81 map.entry(&key, value);
82 }
83 map.finish()
84 }
85}
86
87pub(in crate::db) mod property_keys {
90 pub(in crate::db) const ACCESS_ALTERNATIVES: &str = "acc_alts";
91 pub(in crate::db) const ACCESS_CHOICE: &str = "acc_choice";
92 pub(in crate::db) const ACCESS_REASON: &str = "acc_reason";
93 pub(in crate::db) const ACCESS_REJECTIONS: &str = "acc_reject";
94 pub(in crate::db) const AGGREGATE_CONTRACT: &str = "aggregate_contract";
95 pub(in crate::db) const AGGREGATE_PHYSICAL: &str = "aggregate_physical";
96 pub(in crate::db) const CONTINUATION_MODE: &str = "cont_mode";
97 pub(in crate::db) const COUNT_FOLD: &str = "count_fold";
98 pub(in crate::db) const COVERING_FIELDS: &str = "covering_fields";
99 pub(in crate::db) const COVERING_KIND: &str = "covering_kind";
100 pub(in crate::db) const COVERING_ORDER: &str = "covering_order";
101 pub(in crate::db) const COVERING_READ_KIND: &str = "cov_read_kind";
102 pub(in crate::db) const COVERING_READ_ROUTE: &str = "cov_read_route";
103 pub(in crate::db) const COVERING_SCAN_REASON: &str = "cov_scan_reason";
104 pub(in crate::db) const COVERING_SOURCES: &str = "covering_sources";
105 pub(in crate::db) const EXISTING_ROW_MODE: &str = "existing_row_mode";
106 #[cfg(feature = "sql-explain")]
107 pub(in crate::db) const FILTER_EXPR: &str = "filter_expr";
108 pub(in crate::db) const FAST_PATH: &str = "fast_path";
109 pub(in crate::db) const FAST_REASON: &str = "fast_reason";
110 pub(in crate::db) const FAST_REJECTIONS: &str = "fast_reject";
111 pub(in crate::db) const FETCH: &str = "fetch";
112 pub(in crate::db) const GROUPED_EXECUTION_MODE: &str = "grouped_execution_mode";
113 pub(in crate::db) const GROUPED_PLAN_FALLBACK_REASON: &str = "grouped_plan_fallback_reason";
114 pub(in crate::db) const GROUPED_ROUTE_ELIGIBLE: &str = "grouped_route_eligible";
115 pub(in crate::db) const GROUPED_ROUTE_OUTCOME: &str = "grouped_route_outcome";
116 pub(in crate::db) const GROUPED_ROUTE_REJECTION_REASON: &str = "grouped_route_rejection_reason";
117 #[cfg(feature = "sql-explain")]
118 pub(in crate::db) const AGGREGATE_DIRECT_COUNT_METADATA_ELIGIBLE: &str =
119 "aggregate_direct_count_metadata_eligible";
120 #[cfg(feature = "sql-explain")]
121 pub(in crate::db) const AGGREGATE_DIRECT_COUNT_PREFIXES: &str =
122 "aggregate_direct_count_prefixes";
123 pub(in crate::db) const INDEX: &str = "index";
124 pub(in crate::db) const LIMIT_STOP_AFTER: &str = "limit_stop_after";
125 pub(in crate::db) const OFFSET: &str = "offset";
126 pub(in crate::db) const ORDER_BY_INDEX: &str = "order_by_idx";
127 pub(in crate::db) const ORDER_BY_INDEX_HINT: &str = "order_by_idx_hint";
128 pub(in crate::db) const ORDER_ROUTE_MODE: &str = "ord_route_mode";
129 pub(in crate::db) const ORDER_ROUTE_REASON: &str = "ord_route_reason";
130 pub(in crate::db) const PREDICATE_INDEX_CAPABILITY: &str = "pred_idx_cap";
131 pub(in crate::db) const PREFIX_LEN: &str = "prefix_len";
132 pub(in crate::db) const PREFIX_VALUES: &str = "prefix_values";
133 pub(in crate::db) const PROJECTION_FIELD: &str = "proj_field";
134 pub(in crate::db) const PROJECTION_FIELDS: &str = "proj_fields";
135 #[cfg(feature = "sql-explain")]
136 pub(in crate::db) const PROJECTION_MATERIALIZATION: &str = "proj_materialization";
137 pub(in crate::db) const PROJECTION_MODE: &str = "proj_mode";
138 pub(in crate::db) const PROJECTION_PUSHDOWN: &str = "proj_pushdown";
139 pub(in crate::db) const PUSHDOWN: &str = "pushdown";
140 pub(in crate::db) const RESIDUAL_FILTER_SHAPE: &str = "residual_filter_shape";
141 pub(in crate::db) const RESUME_FROM: &str = "resume_from";
142 pub(in crate::db) const ROUTE_FAMILY: &str = "route_family";
143 pub(in crate::db) const ROUTE_OUTCOME: &str = "route_outcome";
144 pub(in crate::db) const ROUTE_REASON: &str = "route_reason";
145 pub(in crate::db) const SCAN_DIRECTION: &str = "scan_dir";
146 pub(in crate::db) const TERMINAL: &str = "terminal";
147 pub(in crate::db) const TERMINAL_FIELD: &str = "terminal_field";
148 pub(in crate::db) const TERMINAL_INDEX_ONLY: &str = "terminal_index_only";
149 pub(in crate::db) const TERMINAL_OUTPUT: &str = "terminal_output";
150 pub(in crate::db) const TERMINAL_PROJECTION_MODE: &str = "terminal_projection_mode";
151}
152
153pub(in crate::db) mod property_values {
156 pub(in crate::db) const COVERING_READ: &str = "covering_read";
157 #[cfg(feature = "sql-explain")]
158 pub(in crate::db) const DIRECT_SLOT_ROW: &str = "direct_slot_row";
159 pub(in crate::db) const HYBRID_COVERING: &str = "hybrid_covering";
160 pub(in crate::db) const MATERIALIZED: &str = "materialized";
161 pub(in crate::db) const NONE: &str = "none";
162 pub(in crate::db) const PURE_COVERING: &str = "pure_covering";
163 #[cfg(feature = "sql-explain")]
164 pub(in crate::db) const SCALAR_PROJECTION: &str = "scalar_projection";
165 pub(in crate::db) const STRICT_ALL_OR_NONE: &str = "strict_all_or_none";
166}
167
168#[cfg_attr(
169 doc,
170 doc = "ExplainAggregateTerminalPlan\n\nCombined EXPLAIN payload for one scalar aggregate request."
171)]
172#[derive(Clone, Debug, Eq, PartialEq)]
173pub struct ExplainAggregateTerminalPlan {
174 pub(in crate::db) query: ExplainPlan,
175 pub(in crate::db) terminal: AggregateKind,
176 pub(in crate::db) execution: ExplainExecutionDescriptor,
177}
178
179#[cfg_attr(
180 doc,
181 doc = "ExplainExecutionOrderingSource\n\nOrdering-origin label used by execution EXPLAIN output."
182)]
183#[derive(Clone, Copy, Debug, Eq, PartialEq)]
184pub enum ExplainExecutionOrderingSource {
185 AccessOrder,
186 Materialized,
187 IndexSeekFirst { fetch: usize },
188 IndexSeekLast { fetch: usize },
189}
190
191#[cfg_attr(
192 doc,
193 doc = "ExplainExecutionMode\n\nExecution mode used by EXPLAIN descriptors."
194)]
195#[derive(Clone, Copy, Debug, Eq, PartialEq)]
196pub enum ExplainExecutionMode {
197 Streaming,
198 Materialized,
199}
200
201#[cfg_attr(
202 doc,
203 doc = "ExplainExecutionDescriptor\n\nScalar execution descriptor consumed by terminal EXPLAIN surfaces.\nKeeps execution projection centralized for renderers."
204)]
205#[derive(Clone, Debug, Eq, PartialEq)]
206pub struct ExplainExecutionDescriptor {
207 pub(in crate::db) access_strategy: ExplainAccessPath,
208 pub(in crate::db) covering_projection: bool,
209 pub(in crate::db) aggregation: AggregateKind,
210 pub(in crate::db) execution_mode: ExplainExecutionMode,
211 pub(in crate::db) ordering_source: ExplainExecutionOrderingSource,
212 pub(in crate::db) limit: Option<u32>,
213 pub(in crate::db) cursor: bool,
214 pub(in crate::db) node_properties: ExplainPropertyMap,
215}
216
217#[cfg_attr(
218 doc,
219 doc = "ExplainExecutionNodeType\n\nExecution-node vocabulary for EXPLAIN descriptors."
220)]
221#[derive(Clone, Copy, Debug, Eq, PartialEq)]
222pub enum ExplainExecutionNodeType {
223 ByKeyLookup,
224 ByKeysLookup,
225 PrimaryKeyRangeScan,
226 IndexPrefixScan,
227 IndexRangeScan,
228 IndexMultiLookup,
229 IndexBranchSet,
230 FullScan,
231 Union,
232 Intersection,
233 IndexPredicatePrefilter,
234 ResidualFilter,
235 OrderByAccessSatisfied,
236 OrderByMaterializedSort,
237 DistinctPreOrdered,
238 DistinctMaterialized,
239 ProjectionMaterialized,
240 CoveringRead,
241 LimitOffset,
242 CursorResume,
243 IndexRangeLimitPushdown,
244 TopNSeek,
245 AggregateCount,
246 AggregateExists,
247 AggregateMin,
248 AggregateMax,
249 AggregateFirst,
250 AggregateLast,
251 AggregateSum,
252 AggregateSeekFirst,
253 AggregateSeekLast,
254 GroupedAggregateHashMaterialized,
255 GroupedAggregateOrderedMaterialized,
256 SecondaryOrderPushdown,
257}
258
259#[cfg_attr(
260 doc,
261 doc = "ExplainExecutionNodeDescriptor\n\nCanonical execution-node descriptor for EXPLAIN renderers.\nOptional fields are node-family specific."
262)]
263#[derive(Clone, Debug, Eq, PartialEq)]
264pub struct ExplainExecutionNodeDescriptor {
265 pub(in crate::db) node_type: ExplainExecutionNodeType,
266 pub(in crate::db) execution_mode: ExplainExecutionMode,
267 pub(in crate::db) access_strategy: Option<ExplainAccessPath>,
268 pub(in crate::db) predicate_pushdown: Option<String>,
269 pub(in crate::db) filter_expr: Option<String>,
270 pub(in crate::db) residual_filter_expr: Option<String>,
271 pub(in crate::db) residual_filter_predicate: Option<ExplainPredicate>,
272 pub(in crate::db) projection: Option<String>,
273 pub(in crate::db) ordering_source: Option<ExplainExecutionOrderingSource>,
274 pub(in crate::db) limit: Option<u32>,
275 pub(in crate::db) cursor: Option<bool>,
276 pub(in crate::db) covering_scan: Option<bool>,
277 pub(in crate::db) rows_expected: Option<u64>,
278 pub(in crate::db) children: Vec<Self>,
279 pub(in crate::db) node_properties: ExplainPropertyMap,
280}
281
282#[derive(Clone, Debug, Eq, PartialEq)]
293pub(in crate::db) struct FinalizedQueryDiagnostics {
294 pub(in crate::db) execution: ExplainExecutionNodeDescriptor,
295 pub(in crate::db) admission: Option<QueryAdmissionSummary>,
296 pub(in crate::db) route_diagnostics: Vec<String>,
297 pub(in crate::db) logical_diagnostics: Vec<String>,
298 pub(in crate::db) reuse: Option<TraceReuseEvent>,
299}
300
301impl ExplainAggregateTerminalPlan {
302 #[must_use]
304 pub const fn query(&self) -> &ExplainPlan {
305 &self.query
306 }
307
308 #[must_use]
310 pub const fn terminal(&self) -> AggregateKind {
311 self.terminal
312 }
313
314 #[must_use]
316 pub const fn execution(&self) -> &ExplainExecutionDescriptor {
317 &self.execution
318 }
319
320 #[must_use]
321 pub(in crate::db) const fn new(
322 query: ExplainPlan,
323 terminal: AggregateKind,
324 execution: ExplainExecutionDescriptor,
325 ) -> Self {
326 Self {
327 query,
328 terminal,
329 execution,
330 }
331 }
332}
333
334impl ExplainExecutionDescriptor {
335 #[must_use]
337 pub const fn access_strategy(&self) -> &ExplainAccessPath {
338 &self.access_strategy
339 }
340
341 #[must_use]
343 pub const fn covering_projection(&self) -> bool {
344 self.covering_projection
345 }
346
347 #[must_use]
349 pub const fn aggregation(&self) -> AggregateKind {
350 self.aggregation
351 }
352
353 #[must_use]
355 pub const fn execution_mode(&self) -> ExplainExecutionMode {
356 self.execution_mode
357 }
358
359 #[must_use]
361 pub const fn ordering_source(&self) -> ExplainExecutionOrderingSource {
362 self.ordering_source
363 }
364
365 #[must_use]
367 pub const fn limit(&self) -> Option<u32> {
368 self.limit
369 }
370
371 #[must_use]
373 pub const fn cursor(&self) -> bool {
374 self.cursor
375 }
376
377 #[must_use]
379 pub const fn node_properties(&self) -> &ExplainPropertyMap {
380 &self.node_properties
381 }
382}
383
384impl FinalizedQueryDiagnostics {
385 #[must_use]
387 pub(in crate::db) const fn new(
388 execution: ExplainExecutionNodeDescriptor,
389 route_diagnostics: Vec<String>,
390 logical_diagnostics: Vec<String>,
391 reuse: Option<TraceReuseEvent>,
392 ) -> Self {
393 Self {
394 execution,
395 admission: None,
396 route_diagnostics,
397 logical_diagnostics,
398 reuse,
399 }
400 }
401
402 #[must_use]
404 pub(in crate::db) const fn execution(&self) -> &ExplainExecutionNodeDescriptor {
405 &self.execution
406 }
407
408 #[must_use]
410 pub(in crate::db) fn with_admission(mut self, admission: QueryAdmissionSummary) -> Self {
411 self.admission = Some(admission);
412 self
413 }
414
415 #[must_use]
417 pub(in crate::db) const fn admission(&self) -> Option<&QueryAdmissionSummary> {
418 self.admission.as_ref()
419 }
420}
421
422pub(in crate::db) fn annotate_aggregate_execution_identity_properties(
425 node_properties: &mut ExplainPropertyMap,
426 contract: &'static str,
427 physical: &'static str,
428) {
429 node_properties.insert(property_keys::AGGREGATE_CONTRACT, Value::from(contract));
430 node_properties.insert(property_keys::AGGREGATE_PHYSICAL, Value::from(physical));
431}
432
433impl ExplainAggregateTerminalPlan {
434 #[must_use]
436 pub fn execution_node_descriptor(&self) -> ExplainExecutionNodeDescriptor {
437 let mut node_properties = self.execution.node_properties.clone();
438 annotate_aggregate_execution_identity_properties(
439 &mut node_properties,
440 "singleton",
441 scalar_aggregate_physical_label(self.execution.ordering_source),
442 );
443
444 ExplainExecutionNodeDescriptor {
445 node_type: match self.execution.ordering_source {
446 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => {
447 ExplainExecutionNodeType::AggregateSeekFirst
448 }
449 ExplainExecutionOrderingSource::IndexSeekLast { .. } => {
450 ExplainExecutionNodeType::AggregateSeekLast
451 }
452 ExplainExecutionOrderingSource::AccessOrder
453 | ExplainExecutionOrderingSource::Materialized => {
454 self.terminal.explain_execution_node_type()
455 }
456 },
457 execution_mode: self.execution.execution_mode,
458 access_strategy: Some(self.execution.access_strategy.clone()),
459 predicate_pushdown: None,
460 filter_expr: None,
461 residual_filter_expr: None,
462 residual_filter_predicate: None,
463 projection: None,
464 ordering_source: Some(self.execution.ordering_source),
465 limit: self.execution.limit,
466 cursor: Some(self.execution.cursor),
467 covering_scan: Some(self.execution.covering_projection),
468 rows_expected: None,
469 children: Vec::new(),
470 node_properties,
471 }
472 }
473}
474
475const fn scalar_aggregate_physical_label(
476 ordering_source: ExplainExecutionOrderingSource,
477) -> &'static str {
478 match ordering_source {
479 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "scalar_seek_first",
480 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "scalar_seek_last",
481 ExplainExecutionOrderingSource::AccessOrder
482 | ExplainExecutionOrderingSource::Materialized => "scalar_terminal",
483 }
484}
485
486impl AggregateKind {
487 #[must_use]
490 pub(in crate::db) const fn explain_execution_node_type(self) -> ExplainExecutionNodeType {
491 match self {
492 Self::Count => ExplainExecutionNodeType::AggregateCount,
493 Self::Exists => ExplainExecutionNodeType::AggregateExists,
494 Self::Min => ExplainExecutionNodeType::AggregateMin,
495 Self::Max => ExplainExecutionNodeType::AggregateMax,
496 Self::First => ExplainExecutionNodeType::AggregateFirst,
497 Self::Last => ExplainExecutionNodeType::AggregateLast,
498 Self::Sum | Self::Avg => ExplainExecutionNodeType::AggregateSum,
499 }
500 }
501}
502
503impl ExplainExecutionNodeType {
504 #[must_use]
506 pub const fn as_str(self) -> &'static str {
507 match self {
508 Self::ByKeyLookup => "ByKeyLookup",
509 Self::ByKeysLookup => "ByKeysLookup",
510 Self::PrimaryKeyRangeScan => "PrimaryKeyRangeScan",
511 Self::IndexPrefixScan => "IndexPrefixScan",
512 Self::IndexRangeScan => "IndexRangeScan",
513 Self::IndexMultiLookup => "IndexMultiLookup",
514 Self::IndexBranchSet => "IndexBranchSet",
515 Self::FullScan => "FullScan",
516 Self::Union => "Union",
517 Self::Intersection => "Intersection",
518 Self::IndexPredicatePrefilter => "IndexPredicatePrefilter",
519 Self::ResidualFilter => "ResidualFilter",
520 Self::OrderByAccessSatisfied => "OrderByAccessSatisfied",
521 Self::OrderByMaterializedSort => "OrderByMaterializedSort",
522 Self::DistinctPreOrdered => "DistinctPreOrdered",
523 Self::DistinctMaterialized => "DistinctMaterialized",
524 Self::ProjectionMaterialized => "ProjectionMaterialized",
525 Self::CoveringRead => "CoveringRead",
526 Self::LimitOffset => "LimitOffset",
527 Self::CursorResume => "CursorResume",
528 Self::IndexRangeLimitPushdown => "IndexRangeLimitPushdown",
529 Self::TopNSeek => "TopNSeek",
530 Self::AggregateCount => "AggregateCount",
531 Self::AggregateExists => "AggregateExists",
532 Self::AggregateMin => "AggregateMin",
533 Self::AggregateMax => "AggregateMax",
534 Self::AggregateFirst => "AggregateFirst",
535 Self::AggregateLast => "AggregateLast",
536 Self::AggregateSum => "AggregateSum",
537 Self::AggregateSeekFirst => "AggregateSeekFirst",
538 Self::AggregateSeekLast => "AggregateSeekLast",
539 Self::GroupedAggregateHashMaterialized => "GroupedAggregateHashMaterialized",
540 Self::GroupedAggregateOrderedMaterialized => "GroupedAggregateOrderedMaterialized",
541 Self::SecondaryOrderPushdown => "SecondaryOrderPushdown",
542 }
543 }
544
545 #[must_use]
547 pub const fn layer_label(self) -> &'static str {
548 crate::db::query::explain::nodes::layer_label(self)
549 }
550}
551
552impl ExplainExecutionNodeDescriptor {
553 pub(in crate::db) fn for_each_preorder(&self, visit: &mut impl FnMut(&Self)) {
555 visit(self);
556
557 for child in self.children() {
558 child.for_each_preorder(visit);
559 }
560 }
561
562 #[must_use]
564 #[cfg(test)]
565 pub(in crate::db) fn contains_type(&self, target: ExplainExecutionNodeType) -> bool {
566 let mut found = false;
567 self.for_each_preorder(&mut |node| {
568 if node.node_type() == target {
569 found = true;
570 }
571 });
572
573 found
574 }
575
576 #[must_use]
578 pub const fn node_type(&self) -> ExplainExecutionNodeType {
579 self.node_type
580 }
581
582 #[must_use]
584 pub const fn execution_mode(&self) -> ExplainExecutionMode {
585 self.execution_mode
586 }
587
588 #[must_use]
590 pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
591 self.access_strategy.as_ref()
592 }
593
594 #[must_use]
596 pub fn predicate_pushdown(&self) -> Option<&str> {
597 self.predicate_pushdown.as_deref()
598 }
599
600 #[must_use]
602 pub fn filter_expr(&self) -> Option<&str> {
603 self.filter_expr.as_deref()
604 }
605
606 #[must_use]
608 pub fn residual_filter_expr(&self) -> Option<&str> {
609 self.residual_filter_expr.as_deref()
610 }
611
612 #[must_use]
616 pub const fn residual_filter_predicate(&self) -> Option<&ExplainPredicate> {
617 self.residual_filter_predicate.as_ref()
618 }
619
620 #[must_use]
622 pub(in crate::db) const fn residual_filter_shape(&self) -> ResidualFilterShape {
623 ResidualFilterShape::from_presence(
624 self.residual_filter_expr.is_some(),
625 self.residual_filter_predicate.is_some(),
626 )
627 }
628
629 #[must_use]
631 pub const fn has_residual_filter(&self) -> bool {
632 !self.residual_filter_shape().is_absent()
633 }
634
635 #[must_use]
637 pub fn projection(&self) -> Option<&str> {
638 self.projection.as_deref()
639 }
640
641 #[must_use]
643 pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
644 self.ordering_source
645 }
646
647 #[must_use]
649 pub const fn limit(&self) -> Option<u32> {
650 self.limit
651 }
652
653 #[must_use]
655 pub const fn cursor(&self) -> Option<bool> {
656 self.cursor
657 }
658
659 #[must_use]
661 pub const fn covering_scan(&self) -> Option<bool> {
662 self.covering_scan
663 }
664
665 #[must_use]
667 pub const fn rows_expected(&self) -> Option<u64> {
668 self.rows_expected
669 }
670
671 #[must_use]
673 pub const fn children(&self) -> &[Self] {
674 self.children.as_slice()
675 }
676
677 #[must_use]
679 pub const fn node_properties(&self) -> &ExplainPropertyMap {
680 &self.node_properties
681 }
682}
683
684pub(in crate::db::query::explain) const fn execution_mode_label(
685 mode: ExplainExecutionMode,
686) -> &'static str {
687 match mode {
688 ExplainExecutionMode::Streaming => "Streaming",
689 ExplainExecutionMode::Materialized => "Materialized",
690 }
691}
692
693pub(in crate::db::query::explain) const fn ordering_source_label(
694 ordering_source: ExplainExecutionOrderingSource,
695) -> &'static str {
696 match ordering_source {
697 ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
698 ExplainExecutionOrderingSource::Materialized => "Materialized",
699 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
700 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
701 }
702}
703
704#[cfg(test)]
709mod tests {
710 use crate::db::query::explain::{
711 ExplainExecutionMode, ExplainExecutionNodeDescriptor, ExplainExecutionNodeType,
712 ExplainPropertyMap,
713 };
714
715 fn node(
716 node_type: ExplainExecutionNodeType,
717 children: Vec<ExplainExecutionNodeDescriptor>,
718 ) -> ExplainExecutionNodeDescriptor {
719 ExplainExecutionNodeDescriptor {
720 node_type,
721 execution_mode: ExplainExecutionMode::Materialized,
722 access_strategy: None,
723 predicate_pushdown: None,
724 filter_expr: None,
725 residual_filter_expr: None,
726 residual_filter_predicate: None,
727 projection: None,
728 ordering_source: None,
729 limit: None,
730 cursor: None,
731 covering_scan: None,
732 rows_expected: None,
733 children,
734 node_properties: ExplainPropertyMap::new(),
735 }
736 }
737
738 #[test]
739 fn execution_node_contains_type_scans_preorder_tree() {
740 let root = node(
741 ExplainExecutionNodeType::Union,
742 vec![
743 node(ExplainExecutionNodeType::FullScan, Vec::new()),
744 node(
745 ExplainExecutionNodeType::Intersection,
746 vec![node(ExplainExecutionNodeType::ResidualFilter, Vec::new())],
747 ),
748 ],
749 );
750
751 assert!(root.contains_type(ExplainExecutionNodeType::ResidualFilter));
752 assert!(!root.contains_type(ExplainExecutionNodeType::TopNSeek));
753 }
754
755 #[test]
756 fn execution_node_preorder_visits_parent_before_children() {
757 let root = node(
758 ExplainExecutionNodeType::Union,
759 vec![
760 node(ExplainExecutionNodeType::FullScan, Vec::new()),
761 node(
762 ExplainExecutionNodeType::Intersection,
763 vec![node(ExplainExecutionNodeType::ResidualFilter, Vec::new())],
764 ),
765 ],
766 );
767 let mut visited = Vec::new();
768
769 root.for_each_preorder(&mut |node| visited.push(node.node_type()));
770
771 assert_eq!(
772 visited,
773 vec![
774 ExplainExecutionNodeType::Union,
775 ExplainExecutionNodeType::FullScan,
776 ExplainExecutionNodeType::Intersection,
777 ExplainExecutionNodeType::ResidualFilter,
778 ],
779 );
780 }
781}