1use crate::{
7 db::{
8 executor::RouteExecutionMode,
9 query::{
10 explain::{ExplainAccessPath, ExplainPlan, ExplainPredicate},
11 plan::AggregateKind,
12 trace::TraceReuseEvent,
13 },
14 },
15 value::Value,
16};
17use std::fmt::{self, Debug};
18
19#[cfg_attr(
20 doc,
21 doc = "ExplainPropertyMap\n\nStable ordered property map for EXPLAIN metadata.\nKeeps deterministic key order without `BTreeMap`."
22)]
23#[derive(Clone, Default, Eq, PartialEq)]
24pub struct ExplainPropertyMap {
25 entries: Vec<(&'static str, Value)>,
26}
27
28impl ExplainPropertyMap {
29 #[must_use]
31 pub const fn new() -> Self {
32 Self {
33 entries: Vec::new(),
34 }
35 }
36
37 pub fn insert(&mut self, key: &'static str, value: Value) -> Option<Value> {
39 match self
40 .entries
41 .binary_search_by_key(&key, |(existing_key, _)| *existing_key)
42 {
43 Ok(index) => Some(std::mem::replace(&mut self.entries[index].1, value)),
44 Err(index) => {
45 self.entries.insert(index, (key, value));
46 None
47 }
48 }
49 }
50
51 #[must_use]
53 pub fn get(&self, key: &str) -> Option<&Value> {
54 self.entries
55 .binary_search_by_key(&key, |(existing_key, _)| *existing_key)
56 .ok()
57 .map(|index| &self.entries[index].1)
58 }
59
60 #[must_use]
62 pub fn contains_key(&self, key: &str) -> bool {
63 self.get(key).is_some()
64 }
65
66 #[must_use]
68 pub const fn is_empty(&self) -> bool {
69 self.entries.is_empty()
70 }
71
72 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Value)> {
74 self.entries.iter().map(|(key, value)| (*key, value))
75 }
76}
77
78impl Debug for ExplainPropertyMap {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let mut map = f.debug_map();
81 for (key, value) in self.iter() {
82 map.entry(&key, value);
83 }
84 map.finish()
85 }
86}
87
88#[cfg_attr(
89 doc,
90 doc = "ExplainAggregateTerminalPlan\n\nCombined EXPLAIN payload for one scalar aggregate request."
91)]
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct ExplainAggregateTerminalPlan {
94 pub(crate) query: ExplainPlan,
95 pub(crate) terminal: AggregateKind,
96 pub(crate) execution: ExplainExecutionDescriptor,
97}
98
99#[cfg_attr(
100 doc,
101 doc = "ExplainExecutionOrderingSource\n\nOrdering-origin label used by execution EXPLAIN output."
102)]
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
104pub enum ExplainExecutionOrderingSource {
105 AccessOrder,
106 Materialized,
107 IndexSeekFirst { fetch: usize },
108 IndexSeekLast { fetch: usize },
109}
110
111#[cfg_attr(
112 doc,
113 doc = "ExplainExecutionMode\n\nExecution mode used by EXPLAIN descriptors."
114)]
115pub type ExplainExecutionMode = RouteExecutionMode;
116
117#[cfg_attr(
118 doc,
119 doc = "ExplainExecutionDescriptor\n\nScalar execution descriptor consumed by terminal EXPLAIN surfaces.\nKeeps execution projection centralized for renderers."
120)]
121#[derive(Clone, Debug, Eq, PartialEq)]
122pub struct ExplainExecutionDescriptor {
123 pub(crate) access_strategy: ExplainAccessPath,
124 pub(crate) covering_projection: bool,
125 pub(crate) aggregation: AggregateKind,
126 pub(crate) execution_mode: ExplainExecutionMode,
127 pub(crate) ordering_source: ExplainExecutionOrderingSource,
128 pub(crate) limit: Option<u32>,
129 pub(crate) cursor: bool,
130 pub(crate) node_properties: ExplainPropertyMap,
131}
132
133#[cfg_attr(
134 doc,
135 doc = "ExplainExecutionNodeType\n\nExecution-node vocabulary for EXPLAIN descriptors."
136)]
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub enum ExplainExecutionNodeType {
139 ByKeyLookup,
140 ByKeysLookup,
141 PrimaryKeyRangeScan,
142 IndexPrefixScan,
143 IndexRangeScan,
144 IndexMultiLookup,
145 FullScan,
146 Union,
147 Intersection,
148 IndexPredicatePrefilter,
149 ResidualFilter,
150 OrderByAccessSatisfied,
151 OrderByMaterializedSort,
152 DistinctPreOrdered,
153 DistinctMaterialized,
154 ProjectionMaterialized,
155 CoveringRead,
156 LimitOffset,
157 CursorResume,
158 IndexRangeLimitPushdown,
159 TopNSeek,
160 AggregateCount,
161 AggregateExists,
162 AggregateMin,
163 AggregateMax,
164 AggregateFirst,
165 AggregateLast,
166 AggregateSum,
167 AggregateSeekFirst,
168 AggregateSeekLast,
169 GroupedAggregateHashMaterialized,
170 GroupedAggregateOrderedMaterialized,
171 SecondaryOrderPushdown,
172}
173
174#[cfg_attr(
175 doc,
176 doc = "ExplainExecutionNodeDescriptor\n\nCanonical execution-node descriptor for EXPLAIN renderers.\nOptional fields are node-family specific."
177)]
178#[derive(Clone, Debug, Eq, PartialEq)]
179pub struct ExplainExecutionNodeDescriptor {
180 pub(crate) node_type: ExplainExecutionNodeType,
181 pub(crate) execution_mode: ExplainExecutionMode,
182 pub(crate) access_strategy: Option<ExplainAccessPath>,
183 pub(crate) predicate_pushdown: Option<String>,
184 pub(crate) filter_expr: Option<String>,
185 pub(crate) residual_filter_expr: Option<String>,
186 pub(crate) residual_filter_predicate: Option<ExplainPredicate>,
187 pub(crate) projection: Option<String>,
188 pub(crate) ordering_source: Option<ExplainExecutionOrderingSource>,
189 pub(crate) limit: Option<u32>,
190 pub(crate) cursor: Option<bool>,
191 pub(crate) covering_scan: Option<bool>,
192 pub(crate) rows_expected: Option<u64>,
193 pub(crate) children: Vec<Self>,
194 pub(crate) node_properties: ExplainPropertyMap,
195}
196
197#[derive(Clone, Debug, Eq, PartialEq)]
208pub(in crate::db) struct FinalizedQueryDiagnostics {
209 pub(crate) execution: ExplainExecutionNodeDescriptor,
210 pub(crate) route_diagnostics: Vec<String>,
211 pub(crate) logical_diagnostics: Vec<String>,
212 pub(crate) reuse: Option<TraceReuseEvent>,
213}
214
215impl ExplainAggregateTerminalPlan {
216 #[must_use]
218 pub const fn query(&self) -> &ExplainPlan {
219 &self.query
220 }
221
222 #[must_use]
224 pub const fn terminal(&self) -> AggregateKind {
225 self.terminal
226 }
227
228 #[must_use]
230 pub const fn execution(&self) -> &ExplainExecutionDescriptor {
231 &self.execution
232 }
233
234 #[must_use]
235 pub(in crate::db) const fn new(
236 query: ExplainPlan,
237 terminal: AggregateKind,
238 execution: ExplainExecutionDescriptor,
239 ) -> Self {
240 Self {
241 query,
242 terminal,
243 execution,
244 }
245 }
246}
247
248impl ExplainExecutionDescriptor {
249 #[must_use]
251 pub const fn access_strategy(&self) -> &ExplainAccessPath {
252 &self.access_strategy
253 }
254
255 #[must_use]
257 pub const fn covering_projection(&self) -> bool {
258 self.covering_projection
259 }
260
261 #[must_use]
263 pub const fn aggregation(&self) -> AggregateKind {
264 self.aggregation
265 }
266
267 #[must_use]
269 pub const fn execution_mode(&self) -> ExplainExecutionMode {
270 self.execution_mode
271 }
272
273 #[must_use]
275 pub const fn ordering_source(&self) -> ExplainExecutionOrderingSource {
276 self.ordering_source
277 }
278
279 #[must_use]
281 pub const fn limit(&self) -> Option<u32> {
282 self.limit
283 }
284
285 #[must_use]
287 pub const fn cursor(&self) -> bool {
288 self.cursor
289 }
290
291 #[must_use]
293 pub const fn node_properties(&self) -> &ExplainPropertyMap {
294 &self.node_properties
295 }
296}
297
298impl FinalizedQueryDiagnostics {
299 #[must_use]
301 pub(in crate::db) const fn new(
302 execution: ExplainExecutionNodeDescriptor,
303 route_diagnostics: Vec<String>,
304 logical_diagnostics: Vec<String>,
305 reuse: Option<TraceReuseEvent>,
306 ) -> Self {
307 Self {
308 execution,
309 route_diagnostics,
310 logical_diagnostics,
311 reuse,
312 }
313 }
314
315 #[must_use]
317 pub(in crate::db) const fn execution(&self) -> &ExplainExecutionNodeDescriptor {
318 &self.execution
319 }
320}
321
322impl ExplainAggregateTerminalPlan {
323 #[must_use]
325 pub fn execution_node_descriptor(&self) -> ExplainExecutionNodeDescriptor {
326 ExplainExecutionNodeDescriptor {
327 node_type: match self.execution.ordering_source {
328 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => {
329 ExplainExecutionNodeType::AggregateSeekFirst
330 }
331 ExplainExecutionOrderingSource::IndexSeekLast { .. } => {
332 ExplainExecutionNodeType::AggregateSeekLast
333 }
334 ExplainExecutionOrderingSource::AccessOrder
335 | ExplainExecutionOrderingSource::Materialized => {
336 ExplainExecutionNodeType::aggregate_terminal(self.terminal)
337 }
338 },
339 execution_mode: self.execution.execution_mode,
340 access_strategy: Some(self.execution.access_strategy.clone()),
341 predicate_pushdown: None,
342 filter_expr: None,
343 residual_filter_expr: None,
344 residual_filter_predicate: None,
345 projection: None,
346 ordering_source: Some(self.execution.ordering_source),
347 limit: self.execution.limit,
348 cursor: Some(self.execution.cursor),
349 covering_scan: Some(self.execution.covering_projection),
350 rows_expected: None,
351 children: Vec::new(),
352 node_properties: self.execution.node_properties.clone(),
353 }
354 }
355}
356
357impl ExplainExecutionNodeType {
358 #[must_use]
360 pub(in crate::db) const fn aggregate_terminal(kind: AggregateKind) -> Self {
361 match kind {
362 AggregateKind::Count => Self::AggregateCount,
363 AggregateKind::Exists => Self::AggregateExists,
364 AggregateKind::Min => Self::AggregateMin,
365 AggregateKind::Max => Self::AggregateMax,
366 AggregateKind::First => Self::AggregateFirst,
367 AggregateKind::Last => Self::AggregateLast,
368 AggregateKind::Sum | AggregateKind::Avg => Self::AggregateSum,
369 }
370 }
371
372 #[must_use]
374 pub const fn as_str(self) -> &'static str {
375 match self {
376 Self::ByKeyLookup => "ByKeyLookup",
377 Self::ByKeysLookup => "ByKeysLookup",
378 Self::PrimaryKeyRangeScan => "PrimaryKeyRangeScan",
379 Self::IndexPrefixScan => "IndexPrefixScan",
380 Self::IndexRangeScan => "IndexRangeScan",
381 Self::IndexMultiLookup => "IndexMultiLookup",
382 Self::FullScan => "FullScan",
383 Self::Union => "Union",
384 Self::Intersection => "Intersection",
385 Self::IndexPredicatePrefilter => "IndexPredicatePrefilter",
386 Self::ResidualFilter => "ResidualFilter",
387 Self::OrderByAccessSatisfied => "OrderByAccessSatisfied",
388 Self::OrderByMaterializedSort => "OrderByMaterializedSort",
389 Self::DistinctPreOrdered => "DistinctPreOrdered",
390 Self::DistinctMaterialized => "DistinctMaterialized",
391 Self::ProjectionMaterialized => "ProjectionMaterialized",
392 Self::CoveringRead => "CoveringRead",
393 Self::LimitOffset => "LimitOffset",
394 Self::CursorResume => "CursorResume",
395 Self::IndexRangeLimitPushdown => "IndexRangeLimitPushdown",
396 Self::TopNSeek => "TopNSeek",
397 Self::AggregateCount => "AggregateCount",
398 Self::AggregateExists => "AggregateExists",
399 Self::AggregateMin => "AggregateMin",
400 Self::AggregateMax => "AggregateMax",
401 Self::AggregateFirst => "AggregateFirst",
402 Self::AggregateLast => "AggregateLast",
403 Self::AggregateSum => "AggregateSum",
404 Self::AggregateSeekFirst => "AggregateSeekFirst",
405 Self::AggregateSeekLast => "AggregateSeekLast",
406 Self::GroupedAggregateHashMaterialized => "GroupedAggregateHashMaterialized",
407 Self::GroupedAggregateOrderedMaterialized => "GroupedAggregateOrderedMaterialized",
408 Self::SecondaryOrderPushdown => "SecondaryOrderPushdown",
409 }
410 }
411
412 #[must_use]
414 pub const fn layer_label(self) -> &'static str {
415 crate::db::query::explain::nodes::layer_label(self)
416 }
417}
418
419impl ExplainExecutionNodeDescriptor {
420 #[must_use]
422 pub const fn node_type(&self) -> ExplainExecutionNodeType {
423 self.node_type
424 }
425
426 #[must_use]
428 pub const fn execution_mode(&self) -> ExplainExecutionMode {
429 self.execution_mode
430 }
431
432 #[must_use]
434 pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
435 self.access_strategy.as_ref()
436 }
437
438 #[must_use]
440 pub fn predicate_pushdown(&self) -> Option<&str> {
441 self.predicate_pushdown.as_deref()
442 }
443
444 #[must_use]
446 pub fn filter_expr(&self) -> Option<&str> {
447 self.filter_expr.as_deref()
448 }
449
450 #[must_use]
452 pub fn residual_filter_expr(&self) -> Option<&str> {
453 self.residual_filter_expr.as_deref()
454 }
455
456 #[must_use]
460 pub const fn residual_filter_predicate(&self) -> Option<&ExplainPredicate> {
461 self.residual_filter_predicate.as_ref()
462 }
463
464 #[must_use]
466 pub fn projection(&self) -> Option<&str> {
467 self.projection.as_deref()
468 }
469
470 #[must_use]
472 pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
473 self.ordering_source
474 }
475
476 #[must_use]
478 pub const fn limit(&self) -> Option<u32> {
479 self.limit
480 }
481
482 #[must_use]
484 pub const fn cursor(&self) -> Option<bool> {
485 self.cursor
486 }
487
488 #[must_use]
490 pub const fn covering_scan(&self) -> Option<bool> {
491 self.covering_scan
492 }
493
494 #[must_use]
496 pub const fn rows_expected(&self) -> Option<u64> {
497 self.rows_expected
498 }
499
500 #[must_use]
502 pub const fn children(&self) -> &[Self] {
503 self.children.as_slice()
504 }
505
506 #[must_use]
508 pub const fn node_properties(&self) -> &ExplainPropertyMap {
509 &self.node_properties
510 }
511}
512
513pub(in crate::db::query::explain) const fn execution_mode_label(
514 mode: ExplainExecutionMode,
515) -> &'static str {
516 match mode {
517 ExplainExecutionMode::Streaming => "Streaming",
518 ExplainExecutionMode::Materialized => "Materialized",
519 }
520}
521
522pub(in crate::db::query::explain) const fn ordering_source_label(
523 ordering_source: ExplainExecutionOrderingSource,
524) -> &'static str {
525 match ordering_source {
526 ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
527 ExplainExecutionOrderingSource::Materialized => "Materialized",
528 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
529 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
530 }
531}