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