icydb_core/db/session/sql/
explain.rs1use crate::{
7 db::{
8 MissingRowPolicy, QueryError,
9 query::builder::aggregate::AggregateExpr,
10 query::plan::{FieldSlot, resolve_aggregate_target_field_slot},
11 session::sql::surface::{SqlSurface, session_sql_lane, unsupported_sql_lane_message},
12 sql::lowering::{
13 LoweredSqlCommand, SqlGlobalAggregateCommandCore, SqlGlobalAggregateTerminal,
14 bind_lowered_sql_explain_global_aggregate_structural,
15 render_lowered_sql_explain_plan_or_json,
16 },
17 sql::parser::SqlExplainMode,
18 },
19 model::EntityModel,
20};
21
22fn resolve_sql_aggregate_target_slot_with_model(
25 model: &'static EntityModel,
26 field: &str,
27) -> Result<FieldSlot, QueryError> {
28 resolve_aggregate_target_field_slot(model, field)
29}
30
31pub(in crate::db::session::sql) fn resolve_sql_aggregate_target_slot<E>(
32 field: &str,
33) -> Result<FieldSlot, QueryError>
34where
35 E: crate::traits::EntityKind,
36{
37 resolve_sql_aggregate_target_slot_with_model(E::MODEL, field)
38}
39
40fn sql_global_aggregate_terminal_to_expr_with_model(
43 model: &'static EntityModel,
44 terminal: &SqlGlobalAggregateTerminal,
45) -> Result<AggregateExpr, QueryError> {
46 match terminal {
47 SqlGlobalAggregateTerminal::CountRows => Ok(crate::db::query::builder::aggregate::count()),
48 SqlGlobalAggregateTerminal::CountField(field) => {
49 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
50
51 Ok(crate::db::query::builder::aggregate::count_by(
52 field.as_str(),
53 ))
54 }
55 SqlGlobalAggregateTerminal::SumField(field) => {
56 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
57
58 Ok(crate::db::query::builder::aggregate::sum(field.as_str()))
59 }
60 SqlGlobalAggregateTerminal::AvgField(field) => {
61 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
62
63 Ok(crate::db::query::builder::aggregate::avg(field.as_str()))
64 }
65 SqlGlobalAggregateTerminal::MinField(field) => {
66 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
67
68 Ok(crate::db::query::builder::aggregate::min_by(field.as_str()))
69 }
70 SqlGlobalAggregateTerminal::MaxField(field) => {
71 let _ = resolve_sql_aggregate_target_slot_with_model(model, field)?;
72
73 Ok(crate::db::query::builder::aggregate::max_by(field.as_str()))
74 }
75 }
76}
77
78impl LoweredSqlCommand {
79 #[inline(never)]
82 pub fn explain_for_model(&self, model: &'static EntityModel) -> Result<String, QueryError> {
83 let lane = session_sql_lane(self);
86 if lane != crate::db::session::sql::surface::SqlLaneKind::Explain {
87 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
88 SqlSurface::Explain,
89 lane,
90 )));
91 }
92
93 if let Some(rendered) =
96 render_lowered_sql_explain_plan_or_json(self, model, MissingRowPolicy::Ignore)
97 .map_err(QueryError::from_sql_lowering_error)?
98 {
99 return Ok(rendered);
100 }
101
102 if let Some((mode, command)) = bind_lowered_sql_explain_global_aggregate_structural(
105 self,
106 model,
107 MissingRowPolicy::Ignore,
108 ) {
109 return explain_sql_global_aggregate_structural(mode, command);
110 }
111
112 Err(QueryError::unsupported_query(
113 "shared EXPLAIN dispatch could not classify lowered SQL shape",
114 ))
115 }
116}
117
118#[inline(never)]
121fn explain_sql_global_aggregate_structural(
122 mode: SqlExplainMode,
123 command: SqlGlobalAggregateCommandCore,
124) -> Result<String, QueryError> {
125 let model = command.query().model();
126
127 match mode {
128 SqlExplainMode::Plan => {
129 let _ = sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
130
131 Ok(command
132 .query()
133 .build_plan()?
134 .explain_with_model(model)
135 .render_text_canonical())
136 }
137 SqlExplainMode::Execution => {
138 let aggregate =
139 sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
140 let plan = command.query().explain_aggregate_terminal(aggregate)?;
141
142 Ok(plan.execution_node_descriptor().render_text_tree())
143 }
144 SqlExplainMode::Json => {
145 let _ = sql_global_aggregate_terminal_to_expr_with_model(model, command.terminal())?;
146
147 Ok(command
148 .query()
149 .build_plan()?
150 .explain_with_model(model)
151 .render_json_canonical())
152 }
153 }
154}