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 OFFSET: &str = "offset";
125 pub(in crate::db) const ORDER_BY_INDEX: &str = "order_by_idx";
126 pub(in crate::db) const ORDER_ROUTE_MODE: &str = "ord_route_mode";
127 pub(in crate::db) const ORDER_ROUTE_REASON: &str = "ord_route_reason";
128 pub(in crate::db) const PREDICATE_INDEX_CAPABILITY: &str = "pred_idx_cap";
129 pub(in crate::db) const PREFIX_LEN: &str = "prefix_len";
130 pub(in crate::db) const PREFIX_VALUES: &str = "prefix_values";
131 pub(in crate::db) const PROJECTION_FIELD: &str = "proj_field";
132 pub(in crate::db) const PROJECTION_FIELDS: &str = "proj_fields";
133 #[cfg(feature = "sql-explain")]
134 pub(in crate::db) const PROJECTION_MATERIALIZATION: &str = "proj_materialization";
135 pub(in crate::db) const PROJECTION_MODE: &str = "proj_mode";
136 pub(in crate::db) const PROJECTION_PUSHDOWN: &str = "proj_pushdown";
137 pub(in crate::db) const PUSHDOWN: &str = "pushdown";
138 pub(in crate::db) const RESIDUAL_FILTER_SHAPE: &str = "residual_filter_shape";
139 pub(in crate::db) const RESUME_FROM: &str = "resume_from";
140 pub(in crate::db) const SCAN_DIRECTION: &str = "scan_dir";
141 pub(in crate::db) const TERMINAL: &str = "terminal";
142 pub(in crate::db) const TERMINAL_FIELD: &str = "terminal_field";
143 pub(in crate::db) const TERMINAL_INDEX_ONLY: &str = "terminal_index_only";
144 pub(in crate::db) const TERMINAL_OUTPUT: &str = "terminal_output";
145 pub(in crate::db) const TERMINAL_PROJECTION_MODE: &str = "terminal_projection_mode";
146}
147
148pub(in crate::db) mod property_values {
151 pub(in crate::db) const COVERING_READ: &str = "covering_read";
152 #[cfg(feature = "sql-explain")]
153 pub(in crate::db) const DIRECT_SLOT_ROW: &str = "direct_slot_row";
154 pub(in crate::db) const HYBRID_COVERING: &str = "hybrid_covering";
155 pub(in crate::db) const MATERIALIZED: &str = "materialized";
156 pub(in crate::db) const NONE: &str = "none";
157 pub(in crate::db) const PURE_COVERING: &str = "pure_covering";
158 #[cfg(feature = "sql-explain")]
159 pub(in crate::db) const SCALAR_PROJECTION: &str = "scalar_projection";
160 pub(in crate::db) const STRICT_ALL_OR_NONE: &str = "strict_all_or_none";
161}
162
163#[cfg_attr(
164 doc,
165 doc = "ExplainAggregateTerminalPlan\n\nCombined EXPLAIN payload for one scalar aggregate request."
166)]
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct ExplainAggregateTerminalPlan {
169 pub(in crate::db) query: ExplainPlan,
170 pub(in crate::db) terminal: AggregateKind,
171 pub(in crate::db) execution: ExplainExecutionDescriptor,
172}
173
174#[cfg_attr(
175 doc,
176 doc = "ExplainExecutionOrderingSource\n\nOrdering-origin label used by execution EXPLAIN output."
177)]
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179pub enum ExplainExecutionOrderingSource {
180 AccessOrder,
181 Materialized,
182 IndexSeekFirst { fetch: usize },
183 IndexSeekLast { fetch: usize },
184}
185
186#[cfg_attr(
187 doc,
188 doc = "ExplainExecutionMode\n\nExecution mode used by EXPLAIN descriptors."
189)]
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub enum ExplainExecutionMode {
192 Streaming,
193 Materialized,
194}
195
196#[cfg_attr(
197 doc,
198 doc = "ExplainExecutionDescriptor\n\nScalar execution descriptor consumed by terminal EXPLAIN surfaces.\nKeeps execution projection centralized for renderers."
199)]
200#[derive(Clone, Debug, Eq, PartialEq)]
201pub struct ExplainExecutionDescriptor {
202 pub(in crate::db) access_strategy: ExplainAccessPath,
203 pub(in crate::db) covering_projection: bool,
204 pub(in crate::db) aggregation: AggregateKind,
205 pub(in crate::db) execution_mode: ExplainExecutionMode,
206 pub(in crate::db) ordering_source: ExplainExecutionOrderingSource,
207 pub(in crate::db) limit: Option<u32>,
208 pub(in crate::db) cursor: bool,
209 pub(in crate::db) node_properties: ExplainPropertyMap,
210}
211
212#[cfg_attr(
213 doc,
214 doc = "ExplainExecutionNodeType\n\nExecution-node vocabulary for EXPLAIN descriptors."
215)]
216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
217pub enum ExplainExecutionNodeType {
218 ByKeyLookup,
219 ByKeysLookup,
220 PrimaryKeyRangeScan,
221 IndexPrefixScan,
222 IndexRangeScan,
223 IndexMultiLookup,
224 IndexBranchSet,
225 FullScan,
226 Union,
227 Intersection,
228 IndexPredicatePrefilter,
229 ResidualFilter,
230 OrderByAccessSatisfied,
231 OrderByMaterializedSort,
232 DistinctPreOrdered,
233 DistinctMaterialized,
234 ProjectionMaterialized,
235 CoveringRead,
236 LimitOffset,
237 CursorResume,
238 IndexRangeLimitPushdown,
239 TopNSeek,
240 AggregateCount,
241 AggregateExists,
242 AggregateMin,
243 AggregateMax,
244 AggregateFirst,
245 AggregateLast,
246 AggregateSum,
247 AggregateSeekFirst,
248 AggregateSeekLast,
249 GroupedAggregateHashMaterialized,
250 GroupedAggregateOrderedMaterialized,
251 SecondaryOrderPushdown,
252}
253
254#[cfg_attr(
255 doc,
256 doc = "ExplainExecutionNodeDescriptor\n\nCanonical execution-node descriptor for EXPLAIN renderers.\nOptional fields are node-family specific."
257)]
258#[derive(Clone, Debug, Eq, PartialEq)]
259pub struct ExplainExecutionNodeDescriptor {
260 pub(in crate::db) node_type: ExplainExecutionNodeType,
261 pub(in crate::db) execution_mode: ExplainExecutionMode,
262 pub(in crate::db) access_strategy: Option<ExplainAccessPath>,
263 pub(in crate::db) predicate_pushdown: Option<String>,
264 pub(in crate::db) filter_expr: Option<String>,
265 pub(in crate::db) residual_filter_expr: Option<String>,
266 pub(in crate::db) residual_filter_predicate: Option<ExplainPredicate>,
267 pub(in crate::db) projection: Option<String>,
268 pub(in crate::db) ordering_source: Option<ExplainExecutionOrderingSource>,
269 pub(in crate::db) limit: Option<u32>,
270 pub(in crate::db) cursor: Option<bool>,
271 pub(in crate::db) covering_scan: Option<bool>,
272 pub(in crate::db) rows_expected: Option<u64>,
273 pub(in crate::db) children: Vec<Self>,
274 pub(in crate::db) node_properties: ExplainPropertyMap,
275}
276
277#[derive(Clone, Debug, Eq, PartialEq)]
288pub(in crate::db) struct FinalizedQueryDiagnostics {
289 pub(in crate::db) execution: ExplainExecutionNodeDescriptor,
290 pub(in crate::db) admission: Option<QueryAdmissionSummary>,
291 pub(in crate::db) route_diagnostics: Vec<String>,
292 pub(in crate::db) logical_diagnostics: Vec<String>,
293 pub(in crate::db) reuse: Option<TraceReuseEvent>,
294}
295
296impl ExplainAggregateTerminalPlan {
297 #[must_use]
299 pub const fn query(&self) -> &ExplainPlan {
300 &self.query
301 }
302
303 #[must_use]
305 pub const fn terminal(&self) -> AggregateKind {
306 self.terminal
307 }
308
309 #[must_use]
311 pub const fn execution(&self) -> &ExplainExecutionDescriptor {
312 &self.execution
313 }
314
315 #[must_use]
316 pub(in crate::db) const fn new(
317 query: ExplainPlan,
318 terminal: AggregateKind,
319 execution: ExplainExecutionDescriptor,
320 ) -> Self {
321 Self {
322 query,
323 terminal,
324 execution,
325 }
326 }
327}
328
329impl ExplainExecutionDescriptor {
330 #[must_use]
332 pub const fn access_strategy(&self) -> &ExplainAccessPath {
333 &self.access_strategy
334 }
335
336 #[must_use]
338 pub const fn covering_projection(&self) -> bool {
339 self.covering_projection
340 }
341
342 #[must_use]
344 pub const fn aggregation(&self) -> AggregateKind {
345 self.aggregation
346 }
347
348 #[must_use]
350 pub const fn execution_mode(&self) -> ExplainExecutionMode {
351 self.execution_mode
352 }
353
354 #[must_use]
356 pub const fn ordering_source(&self) -> ExplainExecutionOrderingSource {
357 self.ordering_source
358 }
359
360 #[must_use]
362 pub const fn limit(&self) -> Option<u32> {
363 self.limit
364 }
365
366 #[must_use]
368 pub const fn cursor(&self) -> bool {
369 self.cursor
370 }
371
372 #[must_use]
374 pub const fn node_properties(&self) -> &ExplainPropertyMap {
375 &self.node_properties
376 }
377}
378
379impl FinalizedQueryDiagnostics {
380 #[must_use]
382 pub(in crate::db) const fn new(
383 execution: ExplainExecutionNodeDescriptor,
384 route_diagnostics: Vec<String>,
385 logical_diagnostics: Vec<String>,
386 reuse: Option<TraceReuseEvent>,
387 ) -> Self {
388 Self {
389 execution,
390 admission: None,
391 route_diagnostics,
392 logical_diagnostics,
393 reuse,
394 }
395 }
396
397 #[must_use]
399 pub(in crate::db) const fn execution(&self) -> &ExplainExecutionNodeDescriptor {
400 &self.execution
401 }
402
403 #[must_use]
405 pub(in crate::db) fn with_admission(mut self, admission: QueryAdmissionSummary) -> Self {
406 self.admission = Some(admission);
407 self
408 }
409
410 #[must_use]
412 pub(in crate::db) const fn admission(&self) -> Option<&QueryAdmissionSummary> {
413 self.admission.as_ref()
414 }
415}
416
417pub(in crate::db) fn annotate_aggregate_execution_identity_properties(
420 node_properties: &mut ExplainPropertyMap,
421 contract: &'static str,
422 physical: &'static str,
423) {
424 node_properties.insert(property_keys::AGGREGATE_CONTRACT, Value::from(contract));
425 node_properties.insert(property_keys::AGGREGATE_PHYSICAL, Value::from(physical));
426}
427
428impl ExplainAggregateTerminalPlan {
429 #[must_use]
431 pub fn execution_node_descriptor(&self) -> ExplainExecutionNodeDescriptor {
432 let mut node_properties = self.execution.node_properties.clone();
433 annotate_aggregate_execution_identity_properties(
434 &mut node_properties,
435 "singleton",
436 scalar_aggregate_physical_label(self.execution.ordering_source),
437 );
438
439 ExplainExecutionNodeDescriptor {
440 node_type: match self.execution.ordering_source {
441 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => {
442 ExplainExecutionNodeType::AggregateSeekFirst
443 }
444 ExplainExecutionOrderingSource::IndexSeekLast { .. } => {
445 ExplainExecutionNodeType::AggregateSeekLast
446 }
447 ExplainExecutionOrderingSource::AccessOrder
448 | ExplainExecutionOrderingSource::Materialized => {
449 self.terminal.explain_execution_node_type()
450 }
451 },
452 execution_mode: self.execution.execution_mode,
453 access_strategy: Some(self.execution.access_strategy.clone()),
454 predicate_pushdown: None,
455 filter_expr: None,
456 residual_filter_expr: None,
457 residual_filter_predicate: None,
458 projection: None,
459 ordering_source: Some(self.execution.ordering_source),
460 limit: self.execution.limit,
461 cursor: Some(self.execution.cursor),
462 covering_scan: Some(self.execution.covering_projection),
463 rows_expected: None,
464 children: Vec::new(),
465 node_properties,
466 }
467 }
468}
469
470const fn scalar_aggregate_physical_label(
471 ordering_source: ExplainExecutionOrderingSource,
472) -> &'static str {
473 match ordering_source {
474 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "scalar_seek_first",
475 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "scalar_seek_last",
476 ExplainExecutionOrderingSource::AccessOrder
477 | ExplainExecutionOrderingSource::Materialized => "scalar_terminal",
478 }
479}
480
481impl AggregateKind {
482 #[must_use]
485 pub(in crate::db) const fn explain_execution_node_type(self) -> ExplainExecutionNodeType {
486 match self {
487 Self::Count => ExplainExecutionNodeType::AggregateCount,
488 Self::Exists => ExplainExecutionNodeType::AggregateExists,
489 Self::Min => ExplainExecutionNodeType::AggregateMin,
490 Self::Max => ExplainExecutionNodeType::AggregateMax,
491 Self::First => ExplainExecutionNodeType::AggregateFirst,
492 Self::Last => ExplainExecutionNodeType::AggregateLast,
493 Self::Sum | Self::Avg => ExplainExecutionNodeType::AggregateSum,
494 }
495 }
496}
497
498impl ExplainExecutionNodeType {
499 #[must_use]
501 pub const fn as_str(self) -> &'static str {
502 match self {
503 Self::ByKeyLookup => "ByKeyLookup",
504 Self::ByKeysLookup => "ByKeysLookup",
505 Self::PrimaryKeyRangeScan => "PrimaryKeyRangeScan",
506 Self::IndexPrefixScan => "IndexPrefixScan",
507 Self::IndexRangeScan => "IndexRangeScan",
508 Self::IndexMultiLookup => "IndexMultiLookup",
509 Self::IndexBranchSet => "IndexBranchSet",
510 Self::FullScan => "FullScan",
511 Self::Union => "Union",
512 Self::Intersection => "Intersection",
513 Self::IndexPredicatePrefilter => "IndexPredicatePrefilter",
514 Self::ResidualFilter => "ResidualFilter",
515 Self::OrderByAccessSatisfied => "OrderByAccessSatisfied",
516 Self::OrderByMaterializedSort => "OrderByMaterializedSort",
517 Self::DistinctPreOrdered => "DistinctPreOrdered",
518 Self::DistinctMaterialized => "DistinctMaterialized",
519 Self::ProjectionMaterialized => "ProjectionMaterialized",
520 Self::CoveringRead => "CoveringRead",
521 Self::LimitOffset => "LimitOffset",
522 Self::CursorResume => "CursorResume",
523 Self::IndexRangeLimitPushdown => "IndexRangeLimitPushdown",
524 Self::TopNSeek => "TopNSeek",
525 Self::AggregateCount => "AggregateCount",
526 Self::AggregateExists => "AggregateExists",
527 Self::AggregateMin => "AggregateMin",
528 Self::AggregateMax => "AggregateMax",
529 Self::AggregateFirst => "AggregateFirst",
530 Self::AggregateLast => "AggregateLast",
531 Self::AggregateSum => "AggregateSum",
532 Self::AggregateSeekFirst => "AggregateSeekFirst",
533 Self::AggregateSeekLast => "AggregateSeekLast",
534 Self::GroupedAggregateHashMaterialized => "GroupedAggregateHashMaterialized",
535 Self::GroupedAggregateOrderedMaterialized => "GroupedAggregateOrderedMaterialized",
536 Self::SecondaryOrderPushdown => "SecondaryOrderPushdown",
537 }
538 }
539
540 #[must_use]
542 pub const fn layer_label(self) -> &'static str {
543 crate::db::query::explain::nodes::layer_label(self)
544 }
545}
546
547impl ExplainExecutionNodeDescriptor {
548 pub(in crate::db) fn for_each_preorder(&self, visit: &mut impl FnMut(&Self)) {
550 visit(self);
551
552 for child in self.children() {
553 child.for_each_preorder(visit);
554 }
555 }
556
557 #[must_use]
559 #[cfg(test)]
560 pub(in crate::db) fn contains_type(&self, target: ExplainExecutionNodeType) -> bool {
561 let mut found = false;
562 self.for_each_preorder(&mut |node| {
563 if node.node_type() == target {
564 found = true;
565 }
566 });
567
568 found
569 }
570
571 #[must_use]
573 pub const fn node_type(&self) -> ExplainExecutionNodeType {
574 self.node_type
575 }
576
577 #[must_use]
579 pub const fn execution_mode(&self) -> ExplainExecutionMode {
580 self.execution_mode
581 }
582
583 #[must_use]
585 pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
586 self.access_strategy.as_ref()
587 }
588
589 #[must_use]
591 pub fn predicate_pushdown(&self) -> Option<&str> {
592 self.predicate_pushdown.as_deref()
593 }
594
595 #[must_use]
597 pub fn filter_expr(&self) -> Option<&str> {
598 self.filter_expr.as_deref()
599 }
600
601 #[must_use]
603 pub fn residual_filter_expr(&self) -> Option<&str> {
604 self.residual_filter_expr.as_deref()
605 }
606
607 #[must_use]
611 pub const fn residual_filter_predicate(&self) -> Option<&ExplainPredicate> {
612 self.residual_filter_predicate.as_ref()
613 }
614
615 #[must_use]
617 pub(in crate::db) const fn residual_filter_shape(&self) -> ResidualFilterShape {
618 ResidualFilterShape::from_presence(
619 self.residual_filter_expr.is_some(),
620 self.residual_filter_predicate.is_some(),
621 )
622 }
623
624 #[must_use]
626 pub const fn has_residual_filter(&self) -> bool {
627 !self.residual_filter_shape().is_absent()
628 }
629
630 #[must_use]
632 pub fn projection(&self) -> Option<&str> {
633 self.projection.as_deref()
634 }
635
636 #[must_use]
638 pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
639 self.ordering_source
640 }
641
642 #[must_use]
644 pub const fn limit(&self) -> Option<u32> {
645 self.limit
646 }
647
648 #[must_use]
650 pub const fn cursor(&self) -> Option<bool> {
651 self.cursor
652 }
653
654 #[must_use]
656 pub const fn covering_scan(&self) -> Option<bool> {
657 self.covering_scan
658 }
659
660 #[must_use]
662 pub const fn rows_expected(&self) -> Option<u64> {
663 self.rows_expected
664 }
665
666 #[must_use]
668 pub const fn children(&self) -> &[Self] {
669 self.children.as_slice()
670 }
671
672 #[must_use]
674 pub const fn node_properties(&self) -> &ExplainPropertyMap {
675 &self.node_properties
676 }
677}
678
679pub(in crate::db::query::explain) const fn execution_mode_label(
680 mode: ExplainExecutionMode,
681) -> &'static str {
682 match mode {
683 ExplainExecutionMode::Streaming => "Streaming",
684 ExplainExecutionMode::Materialized => "Materialized",
685 }
686}
687
688pub(in crate::db::query::explain) const fn ordering_source_label(
689 ordering_source: ExplainExecutionOrderingSource,
690) -> &'static str {
691 match ordering_source {
692 ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
693 ExplainExecutionOrderingSource::Materialized => "Materialized",
694 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
695 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
696 }
697}
698
699#[cfg(test)]
704mod tests {
705 use crate::db::query::explain::{
706 ExplainExecutionMode, ExplainExecutionNodeDescriptor, ExplainExecutionNodeType,
707 ExplainPropertyMap,
708 };
709
710 fn node(
711 node_type: ExplainExecutionNodeType,
712 children: Vec<ExplainExecutionNodeDescriptor>,
713 ) -> ExplainExecutionNodeDescriptor {
714 ExplainExecutionNodeDescriptor {
715 node_type,
716 execution_mode: ExplainExecutionMode::Materialized,
717 access_strategy: None,
718 predicate_pushdown: None,
719 filter_expr: None,
720 residual_filter_expr: None,
721 residual_filter_predicate: None,
722 projection: None,
723 ordering_source: None,
724 limit: None,
725 cursor: None,
726 covering_scan: None,
727 rows_expected: None,
728 children,
729 node_properties: ExplainPropertyMap::new(),
730 }
731 }
732
733 #[test]
734 fn execution_node_contains_type_scans_preorder_tree() {
735 let root = node(
736 ExplainExecutionNodeType::Union,
737 vec![
738 node(ExplainExecutionNodeType::FullScan, Vec::new()),
739 node(
740 ExplainExecutionNodeType::Intersection,
741 vec![node(ExplainExecutionNodeType::ResidualFilter, Vec::new())],
742 ),
743 ],
744 );
745
746 assert!(root.contains_type(ExplainExecutionNodeType::ResidualFilter));
747 assert!(!root.contains_type(ExplainExecutionNodeType::TopNSeek));
748 }
749
750 #[test]
751 fn execution_node_preorder_visits_parent_before_children() {
752 let root = node(
753 ExplainExecutionNodeType::Union,
754 vec![
755 node(ExplainExecutionNodeType::FullScan, Vec::new()),
756 node(
757 ExplainExecutionNodeType::Intersection,
758 vec![node(ExplainExecutionNodeType::ResidualFilter, Vec::new())],
759 ),
760 ],
761 );
762 let mut visited = Vec::new();
763
764 root.for_each_preorder(&mut |node| visited.push(node.node_type()));
765
766 assert_eq!(
767 visited,
768 vec![
769 ExplainExecutionNodeType::Union,
770 ExplainExecutionNodeType::FullScan,
771 ExplainExecutionNodeType::Intersection,
772 ExplainExecutionNodeType::ResidualFilter,
773 ],
774 );
775 }
776}