icydb_core/db/session/sql/
explain.rs1use crate::{
7 db::{
8 DbSession, MissingRowPolicy, QueryError,
9 executor::{
10 EntityAuthority, assemble_load_execution_node_descriptor_with_model_store_witness,
11 },
12 query::builder::aggregate::AggregateExpr,
13 query::intent::StructuralQuery,
14 query::plan::{FieldSlot, resolve_aggregate_target_field_slot},
15 session::sql::surface::{SqlSurface, session_sql_lane, unsupported_sql_lane_message},
16 sql::lowering::{
17 LoweredSqlCommand, LoweredSqlQuery, SqlGlobalAggregateCommandCore,
18 SqlGlobalAggregateTerminal, apply_lowered_select_shape,
19 bind_lowered_sql_explain_global_aggregate_structural,
20 render_lowered_sql_explain_plan_or_json,
21 },
22 sql::parser::SqlExplainMode,
23 },
24 model::EntityModel,
25 traits::CanisterKind,
26};
27
28fn resolve_sql_aggregate_target_slot_with_model(
31 model: &'static EntityModel,
32 field: &str,
33) -> Result<FieldSlot, QueryError> {
34 resolve_aggregate_target_field_slot(model, field)
35}
36
37pub(in crate::db::session::sql) fn resolve_sql_aggregate_target_slot<E>(
38 field: &str,
39) -> Result<FieldSlot, QueryError>
40where
41 E: crate::traits::EntityKind,
42{
43 resolve_sql_aggregate_target_slot_with_model(E::MODEL, field)
44}
45
46fn sql_global_aggregate_terminal_to_expr_with_model(
49 model: &'static EntityModel,
50 terminal: &SqlGlobalAggregateTerminal,
51) -> Result<AggregateExpr, QueryError> {
52 match terminal {
53 SqlGlobalAggregateTerminal::CountRows => Ok(crate::db::query::builder::aggregate::count()),
54 SqlGlobalAggregateTerminal::CountField(field) => {
55 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
56
57 Ok(crate::db::query::builder::aggregate::count_by(
58 field.as_str(),
59 ))
60 }
61 SqlGlobalAggregateTerminal::SumField(field) => {
62 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
63
64 Ok(crate::db::query::builder::aggregate::sum(field.as_str()))
65 }
66 SqlGlobalAggregateTerminal::AvgField(field) => {
67 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
68
69 Ok(crate::db::query::builder::aggregate::avg(field.as_str()))
70 }
71 SqlGlobalAggregateTerminal::MinField(field) => {
72 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
73
74 Ok(crate::db::query::builder::aggregate::min_by(field.as_str()))
75 }
76 SqlGlobalAggregateTerminal::MaxField(field) => {
77 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
78
79 Ok(crate::db::query::builder::aggregate::max_by(field.as_str()))
80 }
81 }
82}
83
84impl LoweredSqlCommand {
85 #[inline(never)]
88 pub fn explain_for_model(&self, model: &'static EntityModel) -> Result<String, QueryError> {
89 let lane = session_sql_lane(self);
92 if lane != crate::db::session::sql::surface::SqlLaneKind::Explain {
93 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
94 SqlSurface::Explain,
95 lane,
96 )));
97 }
98
99 if let Some(rendered) =
102 render_lowered_sql_explain_plan_or_json(self, model, MissingRowPolicy::Ignore)
103 .map_err(QueryError::from_sql_lowering_error)?
104 {
105 return Ok(rendered);
106 }
107
108 if let Some((mode, command)) = bind_lowered_sql_explain_global_aggregate_structural(
111 self,
112 model,
113 MissingRowPolicy::Ignore,
114 ) {
115 return explain_sql_global_aggregate_structural(mode, command);
116 }
117
118 Err(QueryError::unsupported_query(
119 "shared EXPLAIN dispatch could not classify lowered SQL shape",
120 ))
121 }
122}
123
124impl<C: CanisterKind> DbSession<C> {
125 pub(in crate::db::session::sql) fn explain_lowered_sql_execution_for_authority(
129 &self,
130 lowered: &LoweredSqlCommand,
131 authority: EntityAuthority,
132 ) -> Result<Option<String>, QueryError> {
133 let Some((SqlExplainMode::Execution, query)) = lowered.explain_query() else {
134 return Ok(None);
135 };
136 let LoweredSqlQuery::Select(select) = query else {
137 return Ok(None);
138 };
139
140 let structural = apply_lowered_select_shape(
141 StructuralQuery::new(authority.model(), MissingRowPolicy::Ignore),
142 select.clone(),
143 )
144 .map_err(QueryError::from_sql_lowering_error)?;
145 let plan = structural.build_plan()?;
146 let store = self
147 .db
148 .recovered_store(authority.store_path())
149 .map_err(QueryError::execute)?;
150 let descriptor = assemble_load_execution_node_descriptor_with_model_store_witness(
151 authority.model(),
152 &plan,
153 store,
154 )
155 .map_err(QueryError::execute)?;
156
157 Ok(Some(descriptor.render_text_tree()))
158 }
159}
160
161#[inline(never)]
164fn explain_sql_global_aggregate_structural(
165 mode: SqlExplainMode,
166 command: SqlGlobalAggregateCommandCore,
167) -> Result<String, QueryError> {
168 let model = command.query().model();
169
170 match mode {
171 SqlExplainMode::Plan => {
172 let _ = sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
173
174 Ok(command
175 .query()
176 .build_plan()?
177 .explain_with_model(model)
178 .render_text_canonical())
179 }
180 SqlExplainMode::Execution => {
181 let aggregate =
182 sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
183 let plan = command.query().explain_aggregate_terminal(aggregate)?;
184
185 Ok(plan.execution_node_descriptor().render_text_tree())
186 }
187 SqlExplainMode::Json => {
188 let _ = sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
189
190 Ok(command
191 .query()
192 .build_plan()?
193 .explain_with_model(model)
194 .render_json_canonical())
195 }
196 }
197}