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