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