use crate::db::query::explain::{
ExplainExecutionNodeDescriptor, ExplainPropertyMap, FinalizedQueryDiagnostics,
execution::{execution_mode_label, ordering_source_label},
nodes::{
execution_mode_detail_label, fast_path_reason, fast_path_selected, predicate_pushdown_mode,
},
};
use crate::db::query::plan::explain_access_strategy_label;
use std::fmt::Write;
impl ExplainExecutionNodeDescriptor {
#[must_use]
pub fn render_text_tree(&self) -> String {
let mut out = String::new();
let mut node_id_counter = 0_u64;
self.render_text_tree_into(0, &mut node_id_counter, &mut out);
out
}
#[must_use]
#[cfg(test)]
pub fn render_text_tree_verbose(&self) -> String {
self.render_text_tree_verbose_with_indent("")
}
#[must_use]
pub fn render_text_tree_verbose_with_indent(&self, indent: &str) -> String {
let mut out = String::new();
let mut node_id_counter = 0_u64;
self.render_text_tree_verbose_into(indent, 0, &mut node_id_counter, &mut out);
out
}
fn render_text_tree_into(&self, depth: usize, node_id_counter: &mut u64, out: &mut String) {
let node_id = *node_id_counter;
*node_id_counter = node_id_counter.saturating_add(1);
push_rendered_line_prefix(out, depth);
let _ = write!(
out,
"{} execution_mode={}",
self.node_type.as_str(),
execution_mode_label(self.execution_mode)
);
let _ = write!(out, " node_id={node_id}");
let _ = write!(out, " layer={}", self.node_type.layer_label());
let _ = write!(
out,
" execution_mode_detail={}",
execution_mode_detail_label(self.execution_mode)
);
let _ = write!(
out,
" predicate_pushdown_mode={}",
predicate_pushdown_mode(self)
);
if let Some(fast_path_selected) = fast_path_selected(self) {
let _ = write!(out, " fast_path_selected={fast_path_selected}");
}
if let Some(fast_path_reason) = fast_path_reason(self) {
let _ = write!(out, " fast_path_reason={fast_path_reason}");
}
if let Some(access_strategy) = self.access_strategy.as_ref() {
out.push_str(" access=");
out.push_str(explain_access_strategy_label(access_strategy).as_str());
}
if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
let _ = write!(out, " predicate_pushdown={predicate_pushdown}");
}
if let Some(filter_expr) = self.filter_expr.as_ref() {
let _ = write!(out, " filter_expr={filter_expr}");
}
if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
let _ = write!(out, " residual_filter_expr={residual_filter_expr}");
}
if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
let _ = write!(
out,
" residual_filter_predicate={residual_filter_predicate:?}"
);
}
if let Some(projection) = self.projection.as_ref() {
let _ = write!(out, " projection={projection}");
}
if let Some(ordering_source) = self.ordering_source {
let _ = write!(
out,
" ordering_source={}",
ordering_source_label(ordering_source)
);
}
if let Some(limit) = self.limit {
let _ = write!(out, " limit={limit}");
}
if let Some(cursor) = self.cursor {
let _ = write!(out, " cursor={cursor}");
}
if let Some(covering_scan) = self.covering_scan {
let _ = write!(out, " covering_scan={covering_scan}");
}
if let Some(rows_expected) = self.rows_expected {
let _ = write!(out, " rows_expected={rows_expected}");
}
if !self.node_properties.is_empty() {
out.push_str(" node_properties=");
write_node_properties(out, &self.node_properties);
}
for child in &self.children {
child.render_text_tree_into(depth.saturating_add(1), node_id_counter, out);
}
}
fn render_text_tree_verbose_into(
&self,
base_indent: &str,
depth: usize,
node_id_counter: &mut u64,
out: &mut String,
) {
let node_id = *node_id_counter;
*node_id_counter = node_id_counter.saturating_add(1);
push_rendered_line_prefix_with_base_depth(out, base_indent, depth);
let _ = write!(
out,
"{} execution_mode={}",
self.node_type.as_str(),
execution_mode_label(self.execution_mode)
);
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(out, "node_id={node_id}");
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(out, "layer={}", self.node_type.layer_label());
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(
out,
"execution_mode_detail={}",
execution_mode_detail_label(self.execution_mode)
);
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(
out,
"predicate_pushdown_mode={}",
predicate_pushdown_mode(self)
);
if let Some(fast_path_selected) = fast_path_selected(self) {
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(out, "fast_path_selected={fast_path_selected}");
}
if let Some(fast_path_reason) = fast_path_reason(self) {
push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
let _ = write!(out, "fast_path_reason={fast_path_reason}");
}
self.render_text_tree_verbose_node_fields(base_indent, depth.saturating_add(1), out);
for child in &self.children {
child.render_text_tree_verbose_into(
base_indent,
depth.saturating_add(1),
node_id_counter,
out,
);
}
}
fn render_text_tree_verbose_node_fields(
&self,
base_indent: &str,
field_depth: usize,
out: &mut String,
) {
if let Some(access_strategy) = self.access_strategy.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
out.push_str("access_strategy=");
out.push_str(explain_access_strategy_label(access_strategy).as_str());
}
if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "predicate_pushdown={predicate_pushdown}");
}
if let Some(filter_expr) = self.filter_expr.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "filter_expr={filter_expr}");
}
if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "residual_filter_expr={residual_filter_expr}");
}
if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(
out,
"residual_filter_predicate={residual_filter_predicate:?}"
);
}
if let Some(projection) = self.projection.as_ref() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "projection={projection}");
}
if let Some(ordering_source) = self.ordering_source {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(
out,
"ordering_source={}",
ordering_source_label(ordering_source)
);
}
if let Some(limit) = self.limit {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "limit={limit}");
}
if let Some(cursor) = self.cursor {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "cursor={cursor}");
}
if let Some(covering_scan) = self.covering_scan {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "covering_scan={covering_scan}");
}
if let Some(rows_expected) = self.rows_expected {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
let _ = write!(out, "rows_expected={rows_expected}");
}
if !self.node_properties.is_empty() {
push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
out.push_str("node_properties:");
for (key, value) in self.node_properties.iter() {
push_rendered_line_prefix_with_base_depth(
out,
base_indent,
field_depth.saturating_add(1),
);
let _ = write!(out, "{key}={value:?}");
}
}
}
}
impl FinalizedQueryDiagnostics {
#[must_use]
pub(in crate::db) fn render_text_verbose(&self) -> String {
self.render_text_verbose_with_tree_indent("")
}
#[must_use]
pub(in crate::db) fn render_text_verbose_with_tree_indent(&self, tree_indent: &str) -> String {
let mut lines = vec![
self.execution()
.render_text_tree_verbose_with_indent(tree_indent),
];
lines.extend(self.route_diagnostics.iter().cloned());
lines.extend(self.logical_diagnostics.iter().cloned());
if let Some(reuse) = self.reuse {
let artifact = match reuse.artifact_class() {
crate::db::TraceReuseArtifactClass::SharedPreparedQueryPlan => {
"shared_prepared_query_plan"
}
};
let outcome = if reuse.is_hit() { "hit" } else { "miss" };
lines.push(format!("diag.s.semantic_reuse_artifact={artifact}"));
lines.push(format!("diag.s.semantic_reuse={outcome}"));
}
lines.join("\n")
}
}
fn push_rendered_line_prefix(out: &mut String, depth: usize) {
if !out.is_empty() {
out.push('\n');
}
for _ in 0..depth {
out.push_str(" ");
}
}
fn push_rendered_line_prefix_with_base_depth(out: &mut String, base_indent: &str, depth: usize) {
if !out.is_empty() {
out.push('\n');
}
out.push_str(base_indent);
for _ in 0..depth {
out.push_str(" ");
}
}
fn write_node_properties(out: &mut String, node_properties: &ExplainPropertyMap) {
let mut first = true;
for (key, value) in node_properties.iter() {
if first {
first = false;
} else {
out.push(',');
}
let _ = write!(out, "{key}={value:?}");
}
}