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, freeze_load_execution_route_facts_for_authority,
41};
42
43impl StructuralQuery {
44 fn finalized_execution_diagnostics_from_route_facts(
47 plan: &AccessPlannedQuery,
48 route_facts: &crate::db::executor::explain::descriptor::LoadExecutionRouteFacts,
49 reuse: Option<TraceReuseEvent>,
50 ) -> FinalizedQueryDiagnostics {
51 let descriptor =
52 assemble_load_execution_node_descriptor_from_route_facts(plan, route_facts);
53 let route_diagnostics =
54 assemble_load_execution_verbose_diagnostics_from_route_facts(plan, route_facts);
55 let explain = plan.explain();
56
57 let mut logical_diagnostics = Vec::new();
59 logical_diagnostics.push(format!(
60 "diag.d.has_top_n_seek={}",
61 descriptor.contains_type(ExplainExecutionNodeType::TopNSeek)
62 ));
63 logical_diagnostics.push(format!(
64 "diag.d.has_index_range_limit_pushdown={}",
65 descriptor.contains_type(ExplainExecutionNodeType::IndexRangeLimitPushdown)
66 ));
67 logical_diagnostics.push(format!(
68 "diag.d.has_index_predicate_prefilter={}",
69 descriptor.contains_type(ExplainExecutionNodeType::IndexPredicatePrefilter)
70 ));
71 logical_diagnostics.push(format!(
72 "diag.d.has_residual_filter={}",
73 descriptor.contains_type(ExplainExecutionNodeType::ResidualFilter)
74 ));
75
76 logical_diagnostics.push(format!("diag.p.mode={:?}", explain.mode()));
78 logical_diagnostics.push(format!(
79 "diag.p.order_pushdown={}",
80 plan_order_pushdown_label(explain.order_pushdown())
81 ));
82 logical_diagnostics.push(format!(
83 "diag.p.predicate_pushdown={}",
84 plan_predicate_pushdown_label(explain.predicate(), explain.access())
85 ));
86 logical_diagnostics.push(format!("diag.p.distinct={}", explain.distinct()));
87 logical_diagnostics.push(format!("diag.p.page={:?}", explain.page()));
88 logical_diagnostics.push(format!("diag.p.consistency={:?}", explain.consistency()));
89
90 FinalizedQueryDiagnostics::new(descriptor, route_diagnostics, logical_diagnostics, reuse)
91 }
92
93 pub(in crate::db) fn explain_execution_descriptor_from_plan(
96 &self,
97 plan: &AccessPlannedQuery,
98 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
99 let route_facts = freeze_load_execution_route_facts(
100 self.model().fields(),
101 self.model().primary_key().name(),
102 plan,
103 )
104 .map_err(QueryError::execute)?;
105
106 Ok(assemble_load_execution_node_descriptor_from_route_facts(
107 plan,
108 &route_facts,
109 ))
110 }
111
112 pub(in crate::db) fn explain_execution_descriptor_from_plan_with_authority(
114 &self,
115 plan: &AccessPlannedQuery,
116 authority: &EntityAuthority,
117 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
118 debug_assert_eq!(self.model().path(), authority.entity_path());
119 let route_facts = freeze_load_execution_route_facts_for_authority(authority, plan)
120 .map_err(QueryError::execute)?;
121
122 Ok(assemble_load_execution_node_descriptor_from_route_facts(
123 plan,
124 &route_facts,
125 ))
126 }
127
128 fn finalized_execution_diagnostics_from_plan(
132 &self,
133 plan: &AccessPlannedQuery,
134 reuse: Option<TraceReuseEvent>,
135 ) -> Result<FinalizedQueryDiagnostics, QueryError> {
136 let route_facts = freeze_load_execution_route_facts(
137 self.model().fields(),
138 self.model().primary_key().name(),
139 plan,
140 )
141 .map_err(QueryError::execute)?;
142
143 Ok(Self::finalized_execution_diagnostics_from_route_facts(
144 plan,
145 &route_facts,
146 reuse,
147 ))
148 }
149
150 pub(in crate::db) fn finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
153 &self,
154 plan: &AccessPlannedQuery,
155 authority: &EntityAuthority,
156 reuse: Option<TraceReuseEvent>,
157 mutate_descriptor: impl FnOnce(&mut ExplainExecutionNodeDescriptor),
158 ) -> Result<FinalizedQueryDiagnostics, QueryError> {
159 debug_assert_eq!(self.model().path(), authority.entity_path());
160 let route_facts = freeze_load_execution_route_facts_for_authority(authority, plan)
161 .map_err(QueryError::execute)?;
162 let mut diagnostics =
163 Self::finalized_execution_diagnostics_from_route_facts(plan, &route_facts, reuse);
164 mutate_descriptor(&mut diagnostics.execution);
165
166 Ok(diagnostics)
167 }
168
169 fn explain_execution_verbose_from_plan(
172 &self,
173 plan: &AccessPlannedQuery,
174 ) -> Result<String, QueryError> {
175 self.finalized_execution_diagnostics_from_plan(plan, None)
176 .map(|diagnostics| diagnostics.render_text_verbose())
177 }
178
179 fn finalize_explain_access_choice_for_visibility(
182 &self,
183 plan: &mut AccessPlannedQuery,
184 visible_indexes: Option<&VisibleIndexes<'_>>,
185 ) {
186 let visible_indexes = match visible_indexes {
187 Some(visible_indexes) => visible_indexes.as_slice(),
188 None => self.model().indexes(),
189 };
190
191 plan.finalize_access_choice_for_model_with_indexes(self.model(), visible_indexes);
192 }
193
194 fn explain_execution_descriptor_for_visibility(
197 &self,
198 visible_indexes: Option<&VisibleIndexes<'_>>,
199 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
200 let mut plan = match visible_indexes {
201 Some(visible_indexes) => self.build_plan_with_visible_indexes(visible_indexes)?,
202 None => self.build_plan()?,
203 };
204 self.finalize_explain_access_choice_for_visibility(&mut plan, visible_indexes);
205
206 self.explain_execution_descriptor_from_plan(&plan)
207 }
208
209 fn explain_execution_verbose_for_visibility(
212 &self,
213 visible_indexes: Option<&VisibleIndexes<'_>>,
214 ) -> Result<String, QueryError> {
215 let mut plan = match visible_indexes {
216 Some(visible_indexes) => self.build_plan_with_visible_indexes(visible_indexes)?,
217 None => self.build_plan()?,
218 };
219 self.finalize_explain_access_choice_for_visibility(&mut plan, visible_indexes);
220
221 self.explain_execution_verbose_from_plan(&plan)
222 }
223
224 #[inline(never)]
226 pub(in crate::db) fn explain_execution(
227 &self,
228 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
229 self.explain_execution_descriptor_for_visibility(None)
230 }
231
232 #[inline(never)]
234 pub(in crate::db) fn explain_execution_with_visible_indexes(
235 &self,
236 visible_indexes: &VisibleIndexes<'_>,
237 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
238 self.explain_execution_descriptor_for_visibility(Some(visible_indexes))
239 }
240
241 #[inline(never)]
244 pub(in crate::db) fn explain_execution_verbose(&self) -> Result<String, QueryError> {
245 self.explain_execution_verbose_for_visibility(None)
246 }
247
248 #[inline(never)]
250 pub(in crate::db) fn explain_execution_verbose_with_visible_indexes(
251 &self,
252 visible_indexes: &VisibleIndexes<'_>,
253 ) -> Result<String, QueryError> {
254 self.explain_execution_verbose_for_visibility(Some(visible_indexes))
255 }
256
257 #[inline(never)]
259 #[cfg(test)]
260 pub(in crate::db) fn explain_aggregate_terminal_with_visible_indexes(
261 &self,
262 visible_indexes: &VisibleIndexes<'_>,
263 aggregate: AggregateRouteShape<'_>,
264 ) -> Result<ExplainAggregateTerminalPlan, QueryError> {
265 let plan = self.build_plan_with_visible_indexes(visible_indexes)?;
266 let query_explain = plan.explain();
267 let terminal = aggregate.kind();
268 let execution = assemble_aggregate_terminal_execution_descriptor(&plan, aggregate);
269
270 Ok(ExplainAggregateTerminalPlan::new(
271 query_explain,
272 terminal,
273 execution,
274 ))
275 }
276}
277
278impl<E> PreparedExecutionPlan<E>
279where
280 E: EntityValue + EntityKind,
281{
282 pub(in crate::db) fn explain_prepared_aggregate_terminal<S>(
284 &self,
285 strategy: &S,
286 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
287 where
288 S: AggregateExplain,
289 {
290 let Some(kind) = strategy.explain_aggregate_kind() else {
291 return Err(QueryError::invariant(
292 "prepared fluent aggregate explain requires an explain-visible aggregate kind",
293 ));
294 };
295 let aggregate = self
296 .authority()
297 .aggregate_route_shape(kind, strategy.explain_projected_field())
298 .map_err(QueryError::execute)?;
299 let execution =
300 assemble_aggregate_terminal_execution_descriptor(self.logical_plan(), aggregate);
301
302 Ok(ExplainAggregateTerminalPlan::new(
303 self.logical_plan().explain(),
304 kind,
305 execution,
306 ))
307 }
308
309 pub(in crate::db) fn explain_bytes_by_terminal(
311 &self,
312 target_field: &str,
313 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
314 let mut descriptor = self
315 .explain_load_execution_node_descriptor()
316 .map_err(QueryError::execute)?;
317 let projection_mode = self.bytes_by_projection_mode(target_field);
318 let projection_mode_label = Self::bytes_by_projection_mode_label(projection_mode);
319
320 descriptor
321 .node_properties
322 .insert("terminal", Value::from("bytes_by"));
323 descriptor
324 .node_properties
325 .insert("terminal_field", Value::from(target_field.to_string()));
326 descriptor.node_properties.insert(
327 "terminal_projection_mode",
328 Value::from(projection_mode_label),
329 );
330 descriptor.node_properties.insert(
331 "terminal_index_only",
332 Value::from(matches!(
333 projection_mode,
334 BytesByProjectionMode::CoveringIndex | BytesByProjectionMode::CoveringConstant
335 )),
336 );
337
338 Ok(descriptor)
339 }
340
341 pub(in crate::db) fn explain_prepared_projection_terminal<S>(
343 &self,
344 strategy: &S,
345 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
346 where
347 S: ProjectionExplain,
348 {
349 let mut descriptor = self
350 .explain_load_execution_node_descriptor()
351 .map_err(QueryError::execute)?;
352 let projection_descriptor = strategy.explain_projection_descriptor();
353
354 descriptor.node_properties.insert(
355 "terminal",
356 Value::from(projection_descriptor.terminal_label()),
357 );
358 descriptor.node_properties.insert(
359 "terminal_field",
360 Value::from(projection_descriptor.field_label().to_string()),
361 );
362 descriptor.node_properties.insert(
363 "terminal_output",
364 Value::from(projection_descriptor.output_label()),
365 );
366
367 Ok(descriptor)
368 }
369}
370
371impl<E> Query<E>
372where
373 E: EntityValue + EntityKind,
374{
375 fn explain_execution_descriptor_for_visibility(
378 &self,
379 visible_indexes: Option<&VisibleIndexes<'_>>,
380 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
381 match visible_indexes {
382 Some(visible_indexes) => self
383 .structural()
384 .explain_execution_with_visible_indexes(visible_indexes),
385 None => self.structural().explain_execution(),
386 }
387 }
388
389 fn render_execution_descriptor_for_visibility(
392 &self,
393 visible_indexes: Option<&VisibleIndexes<'_>>,
394 render: impl FnOnce(ExplainExecutionNodeDescriptor) -> String,
395 ) -> Result<String, QueryError> {
396 let descriptor = self.explain_execution_descriptor_for_visibility(visible_indexes)?;
397
398 Ok(render(descriptor))
399 }
400
401 fn explain_execution_verbose_for_visibility(
404 &self,
405 visible_indexes: Option<&VisibleIndexes<'_>>,
406 ) -> Result<String, QueryError> {
407 match visible_indexes {
408 Some(visible_indexes) => self
409 .structural()
410 .explain_execution_verbose_with_visible_indexes(visible_indexes),
411 None => self.structural().explain_execution_verbose(),
412 }
413 }
414
415 pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
417 self.explain_execution_descriptor_for_visibility(None)
418 }
419
420 #[cfg(test)]
422 pub(in crate::db) fn explain_execution_with_visible_indexes(
423 &self,
424 visible_indexes: &VisibleIndexes<'_>,
425 ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
426 self.explain_execution_descriptor_for_visibility(Some(visible_indexes))
427 }
428
429 pub fn explain_execution_text(&self) -> Result<String, QueryError> {
431 self.render_execution_descriptor_for_visibility(None, |descriptor| {
432 descriptor.render_text_tree()
433 })
434 }
435
436 pub fn explain_execution_json(&self) -> Result<String, QueryError> {
438 self.render_execution_descriptor_for_visibility(None, |descriptor| {
439 descriptor.render_json_canonical()
440 })
441 }
442
443 #[inline(never)]
445 pub fn explain_execution_verbose(&self) -> Result<String, QueryError> {
446 self.explain_execution_verbose_for_visibility(None)
447 }
448
449 #[cfg(test)]
451 #[inline(never)]
452 pub(in crate::db) fn explain_aggregate_terminal(
453 &self,
454 aggregate: AggregateExpr,
455 ) -> Result<ExplainAggregateTerminalPlan, QueryError> {
456 self.structural()
457 .explain_aggregate_terminal_with_visible_indexes(
458 &VisibleIndexes::schema_owned(E::MODEL.indexes()),
459 AggregateRouteShape::new_from_fields(
460 aggregate.kind(),
461 aggregate.target_field(),
462 E::MODEL.fields(),
463 E::MODEL.primary_key().name(),
464 ),
465 )
466 }
467}
468
469fn plan_order_pushdown_label(order_pushdown: &ExplainOrderPushdown) -> String {
471 match order_pushdown {
472 ExplainOrderPushdown::MissingModelContext => "missing_model_context".to_string(),
473 ExplainOrderPushdown::EligibleSecondaryIndex { index, prefix_len } => {
474 format!("eligible(index={index},prefix_len={prefix_len})")
475 }
476 ExplainOrderPushdown::Rejected(reason) => format!("rejected({reason:?})"),
477 }
478}
479
480fn plan_predicate_pushdown_label(
482 predicate: &ExplainPredicate,
483 access: &ExplainAccessPath,
484) -> String {
485 let access_label = explain_access_kind_label(access);
486 if matches!(predicate, ExplainPredicate::None) {
487 return "none".to_string();
488 }
489 if access_label == "full_scan" {
490 if explain_predicate_contains_non_strict_compare(predicate) {
491 return "fallback(non_strict_compare_coercion)".to_string();
492 }
493 if explain_predicate_contains_empty_prefix_starts_with(predicate) {
494 return "fallback(starts_with_empty_prefix)".to_string();
495 }
496 if explain_predicate_contains_is_null(predicate) {
497 return "fallback(is_null_full_scan)".to_string();
498 }
499 if explain_predicate_contains_text_scan_operator(predicate) {
500 return "fallback(text_operator_full_scan)".to_string();
501 }
502
503 return format!("fallback({access_label})");
504 }
505
506 format!("applied({access_label})")
507}
508
509fn explain_predicate_contains_non_strict_compare(predicate: &ExplainPredicate) -> bool {
511 match predicate {
512 ExplainPredicate::Compare { coercion, .. }
513 | ExplainPredicate::CompareFields { coercion, .. } => coercion.id != CoercionId::Strict,
514 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
515 .iter()
516 .any(explain_predicate_contains_non_strict_compare),
517 ExplainPredicate::Not(inner) => explain_predicate_contains_non_strict_compare(inner),
518 ExplainPredicate::None
519 | ExplainPredicate::True
520 | ExplainPredicate::False
521 | ExplainPredicate::IsNull { .. }
522 | ExplainPredicate::IsNotNull { .. }
523 | ExplainPredicate::IsMissing { .. }
524 | ExplainPredicate::IsEmpty { .. }
525 | ExplainPredicate::IsNotEmpty { .. }
526 | ExplainPredicate::TextContains { .. }
527 | ExplainPredicate::TextContainsCi { .. } => false,
528 }
529}
530
531fn explain_predicate_contains_is_null(predicate: &ExplainPredicate) -> bool {
533 match predicate {
534 ExplainPredicate::IsNull { .. } => true,
535 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => {
536 children.iter().any(explain_predicate_contains_is_null)
537 }
538 ExplainPredicate::Not(inner) => explain_predicate_contains_is_null(inner),
539 ExplainPredicate::None
540 | ExplainPredicate::True
541 | ExplainPredicate::False
542 | ExplainPredicate::Compare { .. }
543 | ExplainPredicate::CompareFields { .. }
544 | ExplainPredicate::IsNotNull { .. }
545 | ExplainPredicate::IsMissing { .. }
546 | ExplainPredicate::IsEmpty { .. }
547 | ExplainPredicate::IsNotEmpty { .. }
548 | ExplainPredicate::TextContains { .. }
549 | ExplainPredicate::TextContainsCi { .. } => false,
550 }
551}
552
553fn explain_predicate_contains_empty_prefix_starts_with(predicate: &ExplainPredicate) -> bool {
555 match predicate {
556 ExplainPredicate::Compare {
557 op: CompareOp::StartsWith,
558 value: Value::Text(prefix),
559 ..
560 } => prefix.is_empty(),
561 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
562 .iter()
563 .any(explain_predicate_contains_empty_prefix_starts_with),
564 ExplainPredicate::Not(inner) => explain_predicate_contains_empty_prefix_starts_with(inner),
565 ExplainPredicate::None
566 | ExplainPredicate::True
567 | ExplainPredicate::False
568 | ExplainPredicate::Compare { .. }
569 | ExplainPredicate::CompareFields { .. }
570 | ExplainPredicate::IsNull { .. }
571 | ExplainPredicate::IsNotNull { .. }
572 | ExplainPredicate::IsMissing { .. }
573 | ExplainPredicate::IsEmpty { .. }
574 | ExplainPredicate::IsNotEmpty { .. }
575 | ExplainPredicate::TextContains { .. }
576 | ExplainPredicate::TextContainsCi { .. } => false,
577 }
578}
579
580fn explain_predicate_contains_text_scan_operator(predicate: &ExplainPredicate) -> bool {
582 match predicate {
583 ExplainPredicate::Compare {
584 op: CompareOp::EndsWith,
585 ..
586 }
587 | ExplainPredicate::TextContains { .. }
588 | ExplainPredicate::TextContainsCi { .. } => true,
589 ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
590 .iter()
591 .any(explain_predicate_contains_text_scan_operator),
592 ExplainPredicate::Not(inner) => explain_predicate_contains_text_scan_operator(inner),
593 ExplainPredicate::Compare { .. }
594 | ExplainPredicate::CompareFields { .. }
595 | ExplainPredicate::None
596 | ExplainPredicate::True
597 | ExplainPredicate::False
598 | ExplainPredicate::IsNull { .. }
599 | ExplainPredicate::IsNotNull { .. }
600 | ExplainPredicate::IsMissing { .. }
601 | ExplainPredicate::IsEmpty { .. }
602 | ExplainPredicate::IsNotEmpty { .. } => false,
603 }
604}