use crate::{
db::{
DbSession, MissingRowPolicy, QueryError,
executor::{
EntityAuthority,
assemble_load_execution_node_descriptor_with_model_and_visible_indexes,
assemble_prepared_sql_scalar_aggregate_execution_descriptor_with_model,
},
query::explain::ExplainAggregateTerminalPlan,
session::sql::surface::{SqlSurface, session_sql_lane, unsupported_sql_lane_message},
sql::lowering::{
LoweredSqlCommand, LoweredSqlQuery, SqlGlobalAggregateCommandCore,
bind_lowered_sql_explain_global_aggregate_structural,
bind_lowered_sql_query_structural, bind_lowered_sql_select_query_structural,
},
sql::parser::SqlExplainMode,
},
traits::CanisterKind,
};
impl<C: CanisterKind> DbSession<C> {
pub(in crate::db) fn explain_lowered_sql_for_authority(
&self,
lowered: &LoweredSqlCommand,
authority: EntityAuthority,
) -> Result<String, QueryError> {
let lane = session_sql_lane(lowered);
if lane != crate::db::session::sql::surface::SqlLaneKind::Explain {
return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
SqlSurface::Explain,
lane,
)));
}
if let Some(rendered) =
self.render_lowered_sql_explain_plan_or_json_for_authority(lowered, authority)?
{
return Ok(rendered);
}
if let Some((mode, command)) = bind_lowered_sql_explain_global_aggregate_structural(
lowered,
authority.model(),
MissingRowPolicy::Ignore,
) {
return self
.explain_sql_global_aggregate_structural_for_authority(mode, command, authority);
}
Err(QueryError::unsupported_query(
"shared EXPLAIN dispatch could not classify lowered SQL shape",
))
}
fn render_lowered_sql_explain_plan_or_json_for_authority(
&self,
lowered: &LoweredSqlCommand,
authority: EntityAuthority,
) -> Result<Option<String>, QueryError> {
let Some((mode, query)) = lowered.explain_query() else {
return Ok(None);
};
if matches!(mode, SqlExplainMode::Execution) {
return Ok(None);
}
let structural = bind_lowered_sql_query_structural(
authority.model(),
query.clone(),
MissingRowPolicy::Ignore,
)
.map_err(QueryError::from_sql_lowering_error)?;
let visible_indexes =
self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
let plan = structural.build_plan_with_visible_indexes(&visible_indexes)?;
let explain = plan.explain_with_model(authority.model());
let rendered = match mode {
SqlExplainMode::Plan => explain.render_text_canonical(),
SqlExplainMode::Json => explain.render_json_canonical(),
SqlExplainMode::Execution => unreachable!("execution explain is handled separately"),
};
Ok(Some(rendered))
}
pub(in crate::db::session::sql) fn explain_lowered_sql_execution_for_authority(
&self,
lowered: &LoweredSqlCommand,
authority: EntityAuthority,
) -> Result<Option<String>, QueryError> {
let Some((SqlExplainMode::Execution, query)) = lowered.explain_query() else {
return Ok(None);
};
let LoweredSqlQuery::Select(select) = query else {
return Ok(None);
};
let structural = bind_lowered_sql_select_query_structural(
authority.model(),
select.clone(),
MissingRowPolicy::Ignore,
)
.map_err(QueryError::from_sql_lowering_error)?;
let visible_indexes =
self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
let plan = structural.build_plan_with_visible_indexes(&visible_indexes)?;
let descriptor = assemble_load_execution_node_descriptor_with_model_and_visible_indexes(
authority.model(),
&visible_indexes,
&plan,
)
.map_err(QueryError::execute)?;
Ok(Some(descriptor.render_text_tree()))
}
#[inline(never)]
fn explain_sql_global_aggregate_structural_for_authority(
&self,
mode: SqlExplainMode,
command: SqlGlobalAggregateCommandCore,
authority: EntityAuthority,
) -> Result<String, QueryError> {
let model = command.query().model();
let visible_indexes =
self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
let strategy = command
.prepared_scalar_strategy_with_model(model)
.map_err(QueryError::from_sql_lowering_error)?;
match mode {
SqlExplainMode::Plan => Ok(command
.query()
.build_plan_with_visible_indexes(&visible_indexes)?
.explain_with_model(model)
.render_text_canonical()),
SqlExplainMode::Execution => {
let plan = command
.query()
.build_plan_with_visible_indexes(&visible_indexes)?;
let query_explain = plan.explain_with_model(model);
let execution =
assemble_prepared_sql_scalar_aggregate_execution_descriptor_with_model(
model, &plan, &strategy,
);
let terminal_plan = ExplainAggregateTerminalPlan::new(
query_explain,
strategy.aggregate_kind(),
execution,
);
Ok(terminal_plan.execution_node_descriptor().render_text_tree())
}
SqlExplainMode::Json => Ok(command
.query()
.build_plan_with_visible_indexes(&visible_indexes)?
.explain_with_model(model)
.render_json_canonical()),
}
}
}