1mod descriptor;
7
8#[cfg(test)]
9use crate::db::executor::planning::route::AggregateRouteShape;
10#[cfg(test)]
11use crate::db::query::builder::AggregateExpr;
12use crate::{
13 db::{
14 Query, TraceReuseEvent,
15 executor::{BytesByProjectionMode, EntityAuthority, PreparedExecutionPlan},
16 predicate::{CoercionId, CompareOp},
17 query::{
18 builder::{AggregateExplain, ProjectionExplain},
19 explain::{
20 ExplainAccessPath, ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor,
21 ExplainExecutionNodeType, ExplainOrderPushdown, ExplainPredicate,
22 FinalizedQueryDiagnostics,
23 },
24 intent::{QueryError, StructuralQuery},
25 plan::{AccessPlannedQuery, VisibleIndexes, explain_access_kind_label},
26 },
27 },
28 traits::{EntityKind, EntityValue},
29 value::Value,
30};
31
32#[cfg(test)]
33pub(in crate::db) use descriptor::assemble_load_execution_node_descriptor;
34use descriptor::assemble_load_execution_verbose_diagnostics_from_route_facts;
35pub(in crate::db) use descriptor::{
36 assemble_aggregate_terminal_execution_descriptor,
37 assemble_load_execution_node_descriptor_for_authority,
38 assemble_load_execution_node_descriptor_from_route_facts,
39 assemble_scalar_aggregate_execution_descriptor_with_projection,
40 freeze_load_execution_route_facts_for_authority,
41 freeze_load_execution_route_facts_for_model_only,
42};
43
44impl StructuralQuery {
45 fn finalized_execution_diagnostics_from_route_facts(
48 plan: &AccessPlannedQuery,
49 route_facts: &crate::db::executor::explain::descriptor::LoadExecutionRouteFacts,
50 reuse: Option<TraceReuseEvent>,
51 ) -> FinalizedQueryDiagnostics {
52 let descriptor =
53 assemble_load_execution_node_descriptor_from_route_facts(plan, route_facts);
54 let route_diagnostics =
55 assemble_load_execution_verbose_diagnostics_from_route_facts(plan, route_facts);
56 let explain = plan.explain();
57
58 let mut logical_diagnostics = Vec::new();
60 logical_diagnostics.push(format!(
61 "diag.d.has_top_n_seek={}",
62 descriptor.contains_type(ExplainExecutionNodeType::TopNSeek)
63 ));
64 logical_diagnostics.push(format!(
65 "diag.d.has_index_range_limit_pushdown={}",
66 descriptor.contains_type(ExplainExecutionNodeType::IndexRangeLimitPushdown)
67 ));
68 logical_diagnostics.push(format!(
69 "diag.d.has_index_predicate_prefilter={}",
70 descriptor.contains_type(ExplainExecutionNodeType::IndexPredicatePrefilter)
71 ));
72 logical_diagnostics.push(format!(
73 "diag.d.has_residual_filter={}",
74 descriptor.contains_type(ExplainExecutionNodeType::ResidualFilter)
75 ));
76
77 logical_diagnostics.push(format!("diag.p.mode={:?}", explain.mode()));
79 logical_diagnostics.push(format!(
80 "diag.p.order_pushdown={}",
81 plan_order_pushdown_label(explain.order_pushdown())
82 ));
83 logical_diagnostics.push(format!(
84 "diag.p.predicate_pushdown={}",
85 plan_predicate_pushdown_label(explain.predicate(), explain.access())
86 ));
87 logical_diagnostics.push(format!("diag.p.distinct={}", explain.distinct()));
88 logical_diagnostics.push(format!("diag.p.page={:?}", explain.page()));
89 logical_diagnostics.push(format!("diag.p.consistency={:?}", explain.consistency()));
90
91 FinalizedQueryDiagnostics::new(descriptor, route_diagnostics, logical_diagnostics, reuse)
92 }
93
94 pub(in crate::db) fn explain_execution_descriptor_from_model_only_plan(
98 &self,
99 plan: &AccessPlannedQuery,
100 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
101 let route_facts = freeze_load_execution_route_facts_for_model_only(
102 self.model().fields(),
103 self.model().primary_key().name(),
104 plan,
105 )
106 .map_err(QueryError::execute)?;
107
108 Ok(assemble_load_execution_node_descriptor_from_route_facts(
109 plan,
110 &route_facts,
111 ))
112 }
113
114 pub(in crate::db) fn explain_execution_descriptor_from_plan_with_authority(
116 &self,
117 plan: &AccessPlannedQuery,
118 authority: &EntityAuthority,
119 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
120 debug_assert_eq!(self.model().path(), authority.entity_path());
121 let route_facts = freeze_load_execution_route_facts_for_authority(authority, plan)
122 .map_err(QueryError::execute)?;
123
124 Ok(assemble_load_execution_node_descriptor_from_route_facts(
125 plan,
126 &route_facts,
127 ))
128 }
129
130 fn finalized_execution_diagnostics_from_model_only_plan(
135 &self,
136 plan: &AccessPlannedQuery,
137 reuse: Option<TraceReuseEvent>,
138 ) -> Result<FinalizedQueryDiagnostics, QueryError> {
139 let route_facts = freeze_load_execution_route_facts_for_model_only(
140 self.model().fields(),
141 self.model().primary_key().name(),
142 plan,
143 )
144 .map_err(QueryError::execute)?;
145
146 Ok(Self::finalized_execution_diagnostics_from_route_facts(
147 plan,
148 &route_facts,
149 reuse,
150 ))
151 }
152
153 pub(in crate::db) fn finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
156 &self,
157 plan: &AccessPlannedQuery,
158 authority: &EntityAuthority,
159 reuse: Option<TraceReuseEvent>,
160 mutate_descriptor: impl FnOnce(&mut ExplainExecutionNodeDescriptor),
161 ) -> Result<FinalizedQueryDiagnostics, QueryError> {
162 debug_assert_eq!(self.model().path(), authority.entity_path());
163 let route_facts = freeze_load_execution_route_facts_for_authority(authority, plan)
164 .map_err(QueryError::execute)?;
165 let mut diagnostics =
166 Self::finalized_execution_diagnostics_from_route_facts(plan, &route_facts, reuse);
167 mutate_descriptor(&mut diagnostics.execution);
168
169 Ok(diagnostics)
170 }
171
172 fn explain_execution_verbose_from_plan(
175 &self,
176 plan: &AccessPlannedQuery,
177 ) -> Result<String, QueryError> {
178 self.finalized_execution_diagnostics_from_model_only_plan(plan, None)
179 .map(|diagnostics| diagnostics.render_text_verbose())
180 }
181
182 fn finalize_explain_access_choice_for_visible_indexes(
185 &self,
186 plan: &mut AccessPlannedQuery,
187 visible_indexes: &VisibleIndexes<'_>,
188 ) {
189 if let Some(schema_info) = visible_indexes.accepted_schema_info() {
190 plan.finalize_access_choice_for_model_with_accepted_indexes_and_schema(
191 self.model(),
192 visible_indexes.as_slice(),
193 visible_indexes.accepted_field_path_indexes(),
194 schema_info,
195 );
196 return;
197 }
198
199 plan.finalize_access_choice_for_model_only_with_indexes(
200 self.model(),
201 visible_indexes.as_slice(),
202 );
203 }
204
205 fn finalize_explain_access_choice_for_model_only(&self, plan: &mut AccessPlannedQuery) {
209 plan.finalize_access_choice_for_model_only_with_indexes(
210 self.model(),
211 self.model().indexes(),
212 );
213 }
214
215 fn explain_execution_descriptor_for_model_only(
218 &self,
219 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
220 let mut plan = self.build_plan()?;
221 self.finalize_explain_access_choice_for_model_only(&mut plan);
222
223 self.explain_execution_descriptor_from_model_only_plan(&plan)
224 }
225
226 fn explain_execution_descriptor_for_visible_indexes(
229 &self,
230 visible_indexes: &VisibleIndexes<'_>,
231 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
232 let mut plan = self.build_plan_with_visible_indexes(visible_indexes)?;
233 self.finalize_explain_access_choice_for_visible_indexes(&mut plan, visible_indexes);
234
235 self.explain_execution_descriptor_from_model_only_plan(&plan)
236 }
237
238 fn render_execution_verbose_for_model_only(&self) -> Result<String, QueryError> {
241 let mut plan = self.build_plan()?;
242 self.finalize_explain_access_choice_for_model_only(&mut plan);
243
244 self.explain_execution_verbose_from_plan(&plan)
245 }
246
247 fn explain_execution_verbose_for_visible_indexes(
250 &self,
251 visible_indexes: &VisibleIndexes<'_>,
252 ) -> Result<String, QueryError> {
253 let mut plan = self.build_plan_with_visible_indexes(visible_indexes)?;
254 self.finalize_explain_access_choice_for_visible_indexes(&mut plan, visible_indexes);
255
256 self.explain_execution_verbose_from_plan(&plan)
257 }
258
259 #[inline(never)]
261 pub(in crate::db) fn explain_execution_for_model_only(
262 &self,
263 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
264 self.explain_execution_descriptor_for_model_only()
265 }
266
267 #[inline(never)]
269 pub(in crate::db) fn explain_execution_with_visible_indexes(
270 &self,
271 visible_indexes: &VisibleIndexes<'_>,
272 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
273 self.explain_execution_descriptor_for_visible_indexes(visible_indexes)
274 }
275
276 #[inline(never)]
279 pub(in crate::db) fn explain_execution_verbose_for_model_only(
280 &self,
281 ) -> Result<String, QueryError> {
282 self.render_execution_verbose_for_model_only()
283 }
284
285 #[inline(never)]
287 pub(in crate::db) fn explain_execution_verbose_with_visible_indexes(
288 &self,
289 visible_indexes: &VisibleIndexes<'_>,
290 ) -> Result<String, QueryError> {
291 self.explain_execution_verbose_for_visible_indexes(visible_indexes)
292 }
293
294 #[inline(never)]
296 #[cfg(test)]
297 pub(in crate::db) fn explain_aggregate_terminal_with_visible_indexes(
298 &self,
299 visible_indexes: &VisibleIndexes<'_>,
300 aggregate: AggregateRouteShape<'_>,
301 ) -> Result<ExplainAggregateTerminalPlan, QueryError> {
302 let plan = self.build_plan_with_visible_indexes(visible_indexes)?;
303 let query_explain = plan.explain();
304 let terminal = aggregate.kind();
305 let execution = assemble_aggregate_terminal_execution_descriptor(&plan, aggregate);
306
307 Ok(ExplainAggregateTerminalPlan::new(
308 query_explain,
309 terminal,
310 execution,
311 ))
312 }
313}
314
315impl<E> PreparedExecutionPlan<E>
316where
317 E: EntityValue + EntityKind,
318{
319 pub(in crate::db) fn explain_prepared_aggregate_terminal<S>(
321 &self,
322 strategy: &S,
323 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
324 where
325 S: AggregateExplain,
326 {
327 let Some(kind) = strategy.explain_aggregate_kind() else {
328 return Err(QueryError::invariant(
329 "prepared fluent aggregate explain requires an explain-visible aggregate kind",
330 ));
331 };
332 let aggregate = self
333 .authority()
334 .aggregate_route_shape(kind, strategy.explain_projected_field())
335 .map_err(QueryError::execute)?;
336 let execution =
337 assemble_aggregate_terminal_execution_descriptor(self.logical_plan(), aggregate);
338
339 Ok(ExplainAggregateTerminalPlan::new(
340 self.logical_plan().explain(),
341 kind,
342 execution,
343 ))
344 }
345
346 pub(in crate::db) fn explain_bytes_by_terminal(
348 &self,
349 target_field: &str,
350 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
351 let mut descriptor = self
352 .explain_load_execution_node_descriptor()
353 .map_err(QueryError::execute)?;
354 let projection_mode = self.bytes_by_projection_mode(target_field);
355 let projection_mode_label = Self::bytes_by_projection_mode_label(projection_mode);
356
357 descriptor
358 .node_properties
359 .insert("terminal", Value::from("bytes_by"));
360 descriptor
361 .node_properties
362 .insert("terminal_field", Value::from(target_field.to_string()));
363 descriptor.node_properties.insert(
364 "terminal_projection_mode",
365 Value::from(projection_mode_label),
366 );
367 descriptor.node_properties.insert(
368 "terminal_index_only",
369 Value::from(matches!(
370 projection_mode,
371 BytesByProjectionMode::CoveringIndex | BytesByProjectionMode::CoveringConstant
372 )),
373 );
374
375 Ok(descriptor)
376 }
377
378 pub(in crate::db) fn explain_prepared_projection_terminal<S>(
380 &self,
381 strategy: &S,
382 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
383 where
384 S: ProjectionExplain,
385 {
386 let mut descriptor = self
387 .explain_load_execution_node_descriptor()
388 .map_err(QueryError::execute)?;
389 let projection_descriptor = strategy.explain_projection_descriptor();
390
391 descriptor.node_properties.insert(
392 "terminal",
393 Value::from(projection_descriptor.terminal_label()),
394 );
395 descriptor.node_properties.insert(
396 "terminal_field",
397 Value::from(projection_descriptor.field_label().to_string()),
398 );
399 descriptor.node_properties.insert(
400 "terminal_output",
401 Value::from(projection_descriptor.output_label()),
402 );
403
404 Ok(descriptor)
405 }
406}
407
408impl<E> Query<E>
409where
410 E: EntityValue + EntityKind,
411{
412 fn explain_execution_descriptor_for_model_only_or_visible_indexes(
415 &self,
416 visible_indexes: Option<&VisibleIndexes<'_>>,
417 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
418 match visible_indexes {
419 Some(visible_indexes) => self
420 .structural()
421 .explain_execution_with_visible_indexes(visible_indexes),
422 None => self.structural().explain_execution_for_model_only(),
423 }
424 }
425
426 fn render_execution_descriptor_for_visibility(
429 &self,
430 visible_indexes: Option<&VisibleIndexes<'_>>,
431 render: impl FnOnce(ExplainExecutionNodeDescriptor) -> String,
432 ) -> Result<String, QueryError> {
433 let descriptor =
434 self.explain_execution_descriptor_for_model_only_or_visible_indexes(visible_indexes)?;
435
436 Ok(render(descriptor))
437 }
438
439 fn explain_execution_verbose_for_model_only_or_visible_indexes(
442 &self,
443 visible_indexes: Option<&VisibleIndexes<'_>>,
444 ) -> Result<String, QueryError> {
445 match visible_indexes {
446 Some(visible_indexes) => self
447 .structural()
448 .explain_execution_verbose_with_visible_indexes(visible_indexes),
449 None => self.structural().explain_execution_verbose_for_model_only(),
450 }
451 }
452
453 pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
455 self.explain_execution_descriptor_for_model_only_or_visible_indexes(None)
456 }
457
458 #[cfg(test)]
460 pub(in crate::db) fn explain_execution_with_visible_indexes(
461 &self,
462 visible_indexes: &VisibleIndexes<'_>,
463 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
464 self.explain_execution_descriptor_for_model_only_or_visible_indexes(Some(visible_indexes))
465 }
466
467 pub fn explain_execution_text(&self) -> Result<String, QueryError> {
469 self.render_execution_descriptor_for_visibility(None, |descriptor| {
470 descriptor.render_text_tree()
471 })
472 }
473
474 pub fn explain_execution_json(&self) -> Result<String, QueryError> {
476 self.render_execution_descriptor_for_visibility(None, |descriptor| {
477 descriptor.render_json_canonical()
478 })
479 }
480
481 #[inline(never)]
483 pub fn explain_execution_verbose(&self) -> Result<String, QueryError> {
484 self.explain_execution_verbose_for_model_only_or_visible_indexes(None)
485 }
486
487 #[cfg(test)]
489 #[inline(never)]
490 pub(in crate::db) fn explain_aggregate_terminal(
491 &self,
492 aggregate: AggregateExpr,
493 ) -> Result<ExplainAggregateTerminalPlan, QueryError> {
494 self.structural()
495 .explain_aggregate_terminal_with_visible_indexes(
496 &VisibleIndexes::schema_owned(E::MODEL.indexes()),
497 AggregateRouteShape::new_from_fields(
498 aggregate.kind(),
499 aggregate.target_field(),
500 E::MODEL.fields(),
501 E::MODEL.primary_key().name(),
502 ),
503 )
504 }
505}
506
507fn plan_order_pushdown_label(order_pushdown: &ExplainOrderPushdown) -> String {
509 match order_pushdown {
510 ExplainOrderPushdown::MissingModelContext => "missing_model_context".to_string(),
511 ExplainOrderPushdown::EligibleSecondaryIndex { index, prefix_len } => {
512 format!("eligible(index={index},prefix_len={prefix_len})")
513 }
514 ExplainOrderPushdown::Rejected(reason) => format!("rejected({reason:?})"),
515 }
516}
517
518fn plan_predicate_pushdown_label(
520 predicate: &ExplainPredicate,
521 access: &ExplainAccessPath,
522) -> String {
523 let access_label = explain_access_kind_label(access);
524 if matches!(predicate, ExplainPredicate::None) {
525 return "none".to_string();
526 }
527 if access_label == "full_scan" {
528 if explain_predicate_contains_non_strict_compare(predicate) {
529 return "fallback(non_strict_compare_coercion)".to_string();
530 }
531 if explain_predicate_contains_empty_prefix_starts_with(predicate) {
532 return "fallback(starts_with_empty_prefix)".to_string();
533 }
534 if explain_predicate_contains_is_null(predicate) {
535 return "fallback(is_null_full_scan)".to_string();
536 }
537 if explain_predicate_contains_text_scan_operator(predicate) {
538 return "fallback(text_operator_full_scan)".to_string();
539 }
540
541 return format!("fallback({access_label})");
542 }
543
544 format!("applied({access_label})")
545}
546
547fn explain_predicate_contains_non_strict_compare(predicate: &ExplainPredicate) -> bool {
549 match predicate {
550 ExplainPredicate::Compare { coercion, .. }
551 | ExplainPredicate::CompareFields { coercion, .. } => coercion.id != CoercionId::Strict,
552 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
553 .iter()
554 .any(explain_predicate_contains_non_strict_compare),
555 ExplainPredicate::Not(inner) => explain_predicate_contains_non_strict_compare(inner),
556 ExplainPredicate::None
557 | ExplainPredicate::True
558 | ExplainPredicate::False
559 | ExplainPredicate::IsNull { .. }
560 | ExplainPredicate::IsNotNull { .. }
561 | ExplainPredicate::IsMissing { .. }
562 | ExplainPredicate::IsEmpty { .. }
563 | ExplainPredicate::IsNotEmpty { .. }
564 | ExplainPredicate::TextContains { .. }
565 | ExplainPredicate::TextContainsCi { .. } => false,
566 }
567}
568
569fn explain_predicate_contains_is_null(predicate: &ExplainPredicate) -> bool {
571 match predicate {
572 ExplainPredicate::IsNull { .. } => true,
573 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => {
574 children.iter().any(explain_predicate_contains_is_null)
575 }
576 ExplainPredicate::Not(inner) => explain_predicate_contains_is_null(inner),
577 ExplainPredicate::None
578 | ExplainPredicate::True
579 | ExplainPredicate::False
580 | ExplainPredicate::Compare { .. }
581 | ExplainPredicate::CompareFields { .. }
582 | ExplainPredicate::IsNotNull { .. }
583 | ExplainPredicate::IsMissing { .. }
584 | ExplainPredicate::IsEmpty { .. }
585 | ExplainPredicate::IsNotEmpty { .. }
586 | ExplainPredicate::TextContains { .. }
587 | ExplainPredicate::TextContainsCi { .. } => false,
588 }
589}
590
591fn explain_predicate_contains_empty_prefix_starts_with(predicate: &ExplainPredicate) -> bool {
593 match predicate {
594 ExplainPredicate::Compare {
595 op: CompareOp::StartsWith,
596 value: Value::Text(prefix),
597 ..
598 } => prefix.is_empty(),
599 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
600 .iter()
601 .any(explain_predicate_contains_empty_prefix_starts_with),
602 ExplainPredicate::Not(inner) => explain_predicate_contains_empty_prefix_starts_with(inner),
603 ExplainPredicate::None
604 | ExplainPredicate::True
605 | ExplainPredicate::False
606 | ExplainPredicate::Compare { .. }
607 | ExplainPredicate::CompareFields { .. }
608 | ExplainPredicate::IsNull { .. }
609 | ExplainPredicate::IsNotNull { .. }
610 | ExplainPredicate::IsMissing { .. }
611 | ExplainPredicate::IsEmpty { .. }
612 | ExplainPredicate::IsNotEmpty { .. }
613 | ExplainPredicate::TextContains { .. }
614 | ExplainPredicate::TextContainsCi { .. } => false,
615 }
616}
617
618fn explain_predicate_contains_text_scan_operator(predicate: &ExplainPredicate) -> bool {
620 match predicate {
621 ExplainPredicate::Compare {
622 op: CompareOp::EndsWith,
623 ..
624 }
625 | ExplainPredicate::TextContains { .. }
626 | ExplainPredicate::TextContainsCi { .. } => true,
627 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
628 .iter()
629 .any(explain_predicate_contains_text_scan_operator),
630 ExplainPredicate::Not(inner) => explain_predicate_contains_text_scan_operator(inner),
631 ExplainPredicate::Compare { .. }
632 | ExplainPredicate::CompareFields { .. }
633 | ExplainPredicate::None
634 | ExplainPredicate::True
635 | ExplainPredicate::False
636 | ExplainPredicate::IsNull { .. }
637 | ExplainPredicate::IsNotNull { .. }
638 | ExplainPredicate::IsMissing { .. }
639 | ExplainPredicate::IsEmpty { .. }
640 | ExplainPredicate::IsNotEmpty { .. } => false,
641 }
642}