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#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub enum ExplainAggregateTerminalRoute {
95 Standard,
96 IndexSeekFirst { fetch: usize },
97 IndexSeekLast { fetch: usize },
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
107pub struct ExplainAggregateTerminalPlan {
108 pub(crate) query: ExplainPlan,
109 pub(crate) terminal: AggregateKind,
110 pub(crate) route: ExplainAggregateTerminalRoute,
111 pub(crate) execution: ExplainExecutionDescriptor,
112}
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121pub enum ExplainExecutionOrderingSource {
122 AccessOrder,
123 Materialized,
124 IndexSeekFirst { fetch: usize },
125 IndexSeekLast { fetch: usize },
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
135pub enum ExplainExecutionMode {
136 Streaming,
137 Materialized,
138}
139
140#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct ExplainExecutionDescriptor {
149 pub(crate) access_strategy: ExplainAccessPath,
150 pub(crate) covering_projection: bool,
151 pub(crate) aggregation: AggregateKind,
152 pub(crate) execution_mode: ExplainExecutionMode,
153 pub(crate) ordering_source: ExplainExecutionOrderingSource,
154 pub(crate) limit: Option<u32>,
155 pub(crate) cursor: bool,
156 pub(crate) node_properties: ExplainPropertyMap,
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub enum ExplainExecutionNodeType {
167 ByKeyLookup,
168 ByKeysLookup,
169 PrimaryKeyRangeScan,
170 IndexPrefixScan,
171 IndexRangeScan,
172 IndexMultiLookup,
173 FullScan,
174 Union,
175 Intersection,
176 IndexPredicatePrefilter,
177 ResidualPredicateFilter,
178 OrderByAccessSatisfied,
179 OrderByMaterializedSort,
180 DistinctPreOrdered,
181 DistinctMaterialized,
182 ProjectionMaterialized,
183 ProjectionIndexOnly,
184 LimitOffset,
185 CursorResume,
186 IndexRangeLimitPushdown,
187 TopNSeek,
188 AggregateCount,
189 AggregateExists,
190 AggregateMin,
191 AggregateMax,
192 AggregateFirst,
193 AggregateLast,
194 AggregateSum,
195 AggregateSeekFirst,
196 AggregateSeekLast,
197 GroupedAggregateHashMaterialized,
198 GroupedAggregateOrderedMaterialized,
199 SecondaryOrderPushdown,
200}
201
202#[derive(Clone, Debug, Eq, PartialEq)]
210pub struct ExplainExecutionNodeDescriptor {
211 pub(crate) node_type: ExplainExecutionNodeType,
212 pub(crate) execution_mode: ExplainExecutionMode,
213 pub(crate) access_strategy: Option<ExplainAccessPath>,
214 pub(crate) predicate_pushdown: Option<String>,
215 pub(crate) residual_predicate: Option<ExplainPredicate>,
216 pub(crate) projection: Option<String>,
217 pub(crate) ordering_source: Option<ExplainExecutionOrderingSource>,
218 pub(crate) limit: Option<u32>,
219 pub(crate) cursor: Option<bool>,
220 pub(crate) covering_scan: Option<bool>,
221 pub(crate) rows_expected: Option<u64>,
222 pub(crate) children: Vec<Self>,
223 pub(crate) node_properties: ExplainPropertyMap,
224}
225
226impl ExplainAggregateTerminalPlan {
227 #[must_use]
229 pub const fn query(&self) -> &ExplainPlan {
230 &self.query
231 }
232
233 #[must_use]
235 pub const fn terminal(&self) -> AggregateKind {
236 self.terminal
237 }
238
239 #[must_use]
241 pub const fn route(&self) -> ExplainAggregateTerminalRoute {
242 self.route
243 }
244
245 #[must_use]
247 pub const fn execution(&self) -> &ExplainExecutionDescriptor {
248 &self.execution
249 }
250
251 #[must_use]
252 pub(in crate::db) const fn new(
253 query: ExplainPlan,
254 terminal: AggregateKind,
255 execution: ExplainExecutionDescriptor,
256 ) -> Self {
257 let route = execution.route();
258
259 Self {
260 query,
261 terminal,
262 route,
263 execution,
264 }
265 }
266}
267
268impl ExplainExecutionDescriptor {
269 #[must_use]
271 pub const fn access_strategy(&self) -> &ExplainAccessPath {
272 &self.access_strategy
273 }
274
275 #[must_use]
277 pub const fn covering_projection(&self) -> bool {
278 self.covering_projection
279 }
280
281 #[must_use]
283 pub const fn aggregation(&self) -> AggregateKind {
284 self.aggregation
285 }
286
287 #[must_use]
289 pub const fn execution_mode(&self) -> ExplainExecutionMode {
290 self.execution_mode
291 }
292
293 #[must_use]
295 pub const fn ordering_source(&self) -> ExplainExecutionOrderingSource {
296 self.ordering_source
297 }
298
299 #[must_use]
301 pub const fn limit(&self) -> Option<u32> {
302 self.limit
303 }
304
305 #[must_use]
307 pub const fn cursor(&self) -> bool {
308 self.cursor
309 }
310
311 #[must_use]
313 pub const fn node_properties(&self) -> &ExplainPropertyMap {
314 &self.node_properties
315 }
316
317 #[must_use]
318 pub(in crate::db) const fn route(&self) -> ExplainAggregateTerminalRoute {
319 match self.ordering_source {
320 ExplainExecutionOrderingSource::IndexSeekFirst { fetch } => {
321 ExplainAggregateTerminalRoute::IndexSeekFirst { fetch }
322 }
323 ExplainExecutionOrderingSource::IndexSeekLast { fetch } => {
324 ExplainAggregateTerminalRoute::IndexSeekLast { fetch }
325 }
326 ExplainExecutionOrderingSource::AccessOrder
327 | ExplainExecutionOrderingSource::Materialized => {
328 ExplainAggregateTerminalRoute::Standard
329 }
330 }
331 }
332}
333
334impl ExplainAggregateTerminalPlan {
335 #[must_use]
337 pub fn execution_node_descriptor(&self) -> ExplainExecutionNodeDescriptor {
338 ExplainExecutionNodeDescriptor {
339 node_type: aggregate_execution_node_type(self.terminal, self.execution.ordering_source),
340 execution_mode: self.execution.execution_mode,
341 access_strategy: Some(self.execution.access_strategy.clone()),
342 predicate_pushdown: None,
343 residual_predicate: None,
344 projection: None,
345 ordering_source: Some(self.execution.ordering_source),
346 limit: self.execution.limit,
347 cursor: Some(self.execution.cursor),
348 covering_scan: Some(self.execution.covering_projection),
349 rows_expected: None,
350 children: Vec::new(),
351 node_properties: self.execution.node_properties.clone(),
352 }
353 }
354}
355
356const fn aggregate_execution_node_type(
357 terminal: AggregateKind,
358 ordering_source: ExplainExecutionOrderingSource,
359) -> ExplainExecutionNodeType {
360 match ordering_source {
361 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => {
362 ExplainExecutionNodeType::AggregateSeekFirst
363 }
364 ExplainExecutionOrderingSource::IndexSeekLast { .. } => {
365 ExplainExecutionNodeType::AggregateSeekLast
366 }
367 ExplainExecutionOrderingSource::AccessOrder
368 | ExplainExecutionOrderingSource::Materialized => match terminal {
369 AggregateKind::Count => ExplainExecutionNodeType::AggregateCount,
370 AggregateKind::Exists => ExplainExecutionNodeType::AggregateExists,
371 AggregateKind::Min => ExplainExecutionNodeType::AggregateMin,
372 AggregateKind::Max => ExplainExecutionNodeType::AggregateMax,
373 AggregateKind::First => ExplainExecutionNodeType::AggregateFirst,
374 AggregateKind::Last => ExplainExecutionNodeType::AggregateLast,
375 AggregateKind::Sum | AggregateKind::Avg => ExplainExecutionNodeType::AggregateSum,
376 },
377 }
378}
379
380impl ExplainExecutionNodeType {
381 #[must_use]
383 pub const fn as_str(self) -> &'static str {
384 match self {
385 Self::ByKeyLookup => "ByKeyLookup",
386 Self::ByKeysLookup => "ByKeysLookup",
387 Self::PrimaryKeyRangeScan => "PrimaryKeyRangeScan",
388 Self::IndexPrefixScan => "IndexPrefixScan",
389 Self::IndexRangeScan => "IndexRangeScan",
390 Self::IndexMultiLookup => "IndexMultiLookup",
391 Self::FullScan => "FullScan",
392 Self::Union => "Union",
393 Self::Intersection => "Intersection",
394 Self::IndexPredicatePrefilter => "IndexPredicatePrefilter",
395 Self::ResidualPredicateFilter => "ResidualPredicateFilter",
396 Self::OrderByAccessSatisfied => "OrderByAccessSatisfied",
397 Self::OrderByMaterializedSort => "OrderByMaterializedSort",
398 Self::DistinctPreOrdered => "DistinctPreOrdered",
399 Self::DistinctMaterialized => "DistinctMaterialized",
400 Self::ProjectionMaterialized => "ProjectionMaterialized",
401 Self::ProjectionIndexOnly => "ProjectionIndexOnly",
402 Self::LimitOffset => "LimitOffset",
403 Self::CursorResume => "CursorResume",
404 Self::IndexRangeLimitPushdown => "IndexRangeLimitPushdown",
405 Self::TopNSeek => "TopNSeek",
406 Self::AggregateCount => "AggregateCount",
407 Self::AggregateExists => "AggregateExists",
408 Self::AggregateMin => "AggregateMin",
409 Self::AggregateMax => "AggregateMax",
410 Self::AggregateFirst => "AggregateFirst",
411 Self::AggregateLast => "AggregateLast",
412 Self::AggregateSum => "AggregateSum",
413 Self::AggregateSeekFirst => "AggregateSeekFirst",
414 Self::AggregateSeekLast => "AggregateSeekLast",
415 Self::GroupedAggregateHashMaterialized => "GroupedAggregateHashMaterialized",
416 Self::GroupedAggregateOrderedMaterialized => "GroupedAggregateOrderedMaterialized",
417 Self::SecondaryOrderPushdown => "SecondaryOrderPushdown",
418 }
419 }
420
421 #[must_use]
423 pub const fn layer_label(self) -> &'static str {
424 crate::db::query::explain::nodes::layer_label(self)
425 }
426}
427
428impl ExplainExecutionNodeDescriptor {
429 #[must_use]
431 pub const fn node_type(&self) -> ExplainExecutionNodeType {
432 self.node_type
433 }
434
435 #[must_use]
437 pub const fn execution_mode(&self) -> ExplainExecutionMode {
438 self.execution_mode
439 }
440
441 #[must_use]
443 pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
444 self.access_strategy.as_ref()
445 }
446
447 #[must_use]
449 pub fn predicate_pushdown(&self) -> Option<&str> {
450 self.predicate_pushdown.as_deref()
451 }
452
453 #[must_use]
455 pub const fn residual_predicate(&self) -> Option<&ExplainPredicate> {
456 self.residual_predicate.as_ref()
457 }
458
459 #[must_use]
461 pub fn projection(&self) -> Option<&str> {
462 self.projection.as_deref()
463 }
464
465 #[must_use]
467 pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
468 self.ordering_source
469 }
470
471 #[must_use]
473 pub const fn limit(&self) -> Option<u32> {
474 self.limit
475 }
476
477 #[must_use]
479 pub const fn cursor(&self) -> Option<bool> {
480 self.cursor
481 }
482
483 #[must_use]
485 pub const fn covering_scan(&self) -> Option<bool> {
486 self.covering_scan
487 }
488
489 #[must_use]
491 pub const fn rows_expected(&self) -> Option<u64> {
492 self.rows_expected
493 }
494
495 #[must_use]
497 pub const fn children(&self) -> &[Self] {
498 self.children.as_slice()
499 }
500
501 #[must_use]
503 pub const fn node_properties(&self) -> &ExplainPropertyMap {
504 &self.node_properties
505 }
506}
507
508pub(in crate::db::query::explain) const fn execution_mode_label(
509 mode: ExplainExecutionMode,
510) -> &'static str {
511 match mode {
512 ExplainExecutionMode::Streaming => "Streaming",
513 ExplainExecutionMode::Materialized => "Materialized",
514 }
515}
516
517pub(in crate::db::query::explain) const fn ordering_source_label(
518 ordering_source: ExplainExecutionOrderingSource,
519) -> &'static str {
520 match ordering_source {
521 ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
522 ExplainExecutionOrderingSource::Materialized => "Materialized",
523 ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
524 ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
525 }
526}