icydb_core/db/session/sql/dispatch/
mod.rs1mod computed;
7mod lowered;
8
9use crate::{
10 db::{
11 DbSession, MissingRowPolicy, PersistedRow, Query, QueryError,
12 executor::{EntityAuthority, execute_sql_projection_rows_for_canister},
13 query::intent::StructuralQuery,
14 session::sql::{
15 SqlDispatchResult, SqlParsedStatement, SqlStatementRoute, computed_projection,
16 projection::{
17 SqlProjectionPayload, projection_labels_from_entity_model,
18 projection_labels_from_structural_query, sql_projection_rows_from_kernel_rows,
19 },
20 surface::{SqlSurface, session_sql_lane, unsupported_sql_lane_message},
21 },
22 sql::lowering::{
23 LoweredSqlQuery, bind_lowered_sql_query, lower_sql_command_from_prepared_statement,
24 },
25 },
26 traits::{CanisterKind, EntityKind, EntityValue},
27};
28
29impl<C: CanisterKind> DbSession<C> {
30 pub(in crate::db::session::sql) fn bind_sql_query_lane_from_parsed<E>(
33 parsed: &SqlParsedStatement,
34 ) -> Result<(LoweredSqlQuery, Query<E>), QueryError>
35 where
36 E: EntityKind<Canister = C>,
37 {
38 if computed_projection::computed_sql_projection_plan(&parsed.statement)?.is_some() {
42 return Err(QueryError::unsupported_query(
43 "query_from_sql does not accept computed text projection; use execute_sql_dispatch(...)",
44 ));
45 }
46
47 let lowered =
48 parsed.lower_query_lane_for_entity(E::MODEL.name(), E::MODEL.primary_key.name)?;
49 let lane = session_sql_lane(&lowered);
50 let Some(query) = lowered.query().cloned() else {
51 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
52 SqlSurface::QueryFrom,
53 lane,
54 )));
55 };
56 let typed = bind_lowered_sql_query::<E>(query.clone(), MissingRowPolicy::Ignore)
57 .map_err(QueryError::from_sql_lowering_error)?;
58
59 Ok((query, typed))
60 }
61
62 fn execute_structural_sql_projection(
66 &self,
67 query: StructuralQuery,
68 authority: EntityAuthority,
69 ) -> Result<SqlProjectionPayload, QueryError> {
70 let columns = projection_labels_from_structural_query(&query)?;
71 let projected = execute_sql_projection_rows_for_canister(
72 &self.db,
73 self.debug,
74 authority,
75 query.build_plan()?,
76 )
77 .map_err(QueryError::execute)?;
78 let (rows, row_count) = projected.into_parts();
79
80 Ok(SqlProjectionPayload::new(columns, rows, row_count))
81 }
82
83 fn execute_typed_sql_delete<E>(&self, query: &Query<E>) -> Result<SqlDispatchResult, QueryError>
87 where
88 E: PersistedRow<Canister = C> + EntityValue,
89 {
90 let plan = query.plan()?.into_executable();
91 let deleted = self
92 .with_metrics(|| self.delete_executor::<E>().execute_sql_projection(plan))
93 .map_err(QueryError::execute)?;
94 let (rows, row_count) = deleted.into_parts();
95 let rows = sql_projection_rows_from_kernel_rows(rows);
96
97 Ok(SqlProjectionPayload::new(
98 projection_labels_from_entity_model(E::MODEL),
99 rows,
100 row_count,
101 )
102 .into_dispatch_result())
103 }
104
105 pub(in crate::db::session::sql) fn ensure_sql_query_grouping<E>(
108 query: &Query<E>,
109 grouped: bool,
110 ) -> Result<(), QueryError>
111 where
112 E: EntityKind,
113 {
114 match (grouped, query.has_grouping()) {
115 (true, true) | (false, false) => Ok(()),
116 (false, true) => Err(QueryError::grouped_requires_execute_grouped()),
117 (true, false) => Err(QueryError::unsupported_query(
118 "execute_sql_grouped requires grouped SQL query intent",
119 )),
120 }
121 }
122
123 pub fn execute_sql_dispatch<E>(&self, sql: &str) -> Result<SqlDispatchResult, QueryError>
125 where
126 E: PersistedRow<Canister = C> + EntityValue,
127 {
128 let parsed = self.parse_sql_statement(sql)?;
129
130 self.execute_sql_dispatch_parsed::<E>(&parsed)
131 }
132
133 pub fn execute_sql_dispatch_parsed<E>(
135 &self,
136 parsed: &SqlParsedStatement,
137 ) -> Result<SqlDispatchResult, QueryError>
138 where
139 E: PersistedRow<Canister = C> + EntityValue,
140 {
141 match parsed.route() {
142 SqlStatementRoute::Query { .. } => {
143 if let Some(plan) =
144 computed_projection::computed_sql_projection_plan(&parsed.statement)?
145 {
146 return self.execute_computed_sql_projection_dispatch::<E>(plan);
147 }
148
149 let (query, typed_query) = Self::bind_sql_query_lane_from_parsed::<E>(parsed)?;
150
151 Self::ensure_sql_query_grouping(&typed_query, false)?;
152
153 match query {
154 LoweredSqlQuery::Select(select) => self
155 .execute_lowered_sql_dispatch_select_core(
156 &select,
157 EntityAuthority::for_type::<E>(),
158 ),
159 LoweredSqlQuery::Delete(_) => self.execute_typed_sql_delete(&typed_query),
160 }
161 }
162 SqlStatementRoute::Explain { .. } => {
163 if let Some((mode, plan)) =
164 computed_projection::computed_sql_projection_explain_plan(&parsed.statement)?
165 {
166 return Self::explain_computed_sql_projection_dispatch::<E>(mode, plan)
167 .map(SqlDispatchResult::Explain);
168 }
169
170 let lowered = lower_sql_command_from_prepared_statement(
171 parsed.prepare(E::MODEL.name())?,
172 E::MODEL.primary_key.name,
173 )
174 .map_err(QueryError::from_sql_lowering_error)?;
175
176 lowered
177 .explain_for_model(E::MODEL)
178 .map(SqlDispatchResult::Explain)
179 }
180 SqlStatementRoute::Describe { .. } => {
181 Ok(SqlDispatchResult::Describe(self.describe_entity::<E>()))
182 }
183 SqlStatementRoute::ShowIndexes { .. } => {
184 Ok(SqlDispatchResult::ShowIndexes(self.show_indexes::<E>()))
185 }
186 SqlStatementRoute::ShowColumns { .. } => {
187 Ok(SqlDispatchResult::ShowColumns(self.show_columns::<E>()))
188 }
189 SqlStatementRoute::ShowEntities => {
190 Ok(SqlDispatchResult::ShowEntities(self.show_entities()))
191 }
192 }
193 }
194
195 #[doc(hidden)]
202 pub fn execute_generated_query_surface_dispatch_for_authority(
203 &self,
204 parsed: &SqlParsedStatement,
205 authority: EntityAuthority,
206 ) -> Result<SqlDispatchResult, QueryError> {
207 match parsed.route() {
208 SqlStatementRoute::Query { .. } => {
209 if let Some(plan) =
210 computed_projection::computed_sql_projection_plan(&parsed.statement)?
211 {
212 return self
213 .execute_computed_sql_projection_dispatch_for_authority(plan, authority);
214 }
215
216 let lowered = parsed.lower_generated_query_surface_for_entity(
217 authority.model().name(),
218 authority.model().primary_key.name,
219 )?;
220
221 self.execute_lowered_sql_dispatch_query_for_authority(&lowered, authority)
222 }
223 SqlStatementRoute::Explain { .. } => {
224 if let Some((mode, plan)) =
225 computed_projection::computed_sql_projection_explain_plan(&parsed.statement)?
226 {
227 return Self::explain_computed_sql_projection_dispatch_for_authority(
228 mode, plan, authority,
229 )
230 .map(SqlDispatchResult::Explain);
231 }
232
233 let lowered = parsed.lower_generated_query_surface_for_entity(
234 authority.model().name(),
235 authority.model().primary_key.name,
236 )?;
237
238 lowered
239 .explain_for_model(authority.model())
240 .map(SqlDispatchResult::Explain)
241 }
242 SqlStatementRoute::Describe { .. }
243 | SqlStatementRoute::ShowIndexes { .. }
244 | SqlStatementRoute::ShowColumns { .. }
245 | SqlStatementRoute::ShowEntities => Err(QueryError::unsupported_query(
246 "generated SQL query surface requires query or EXPLAIN statement lanes",
247 )),
248 }
249 }
250}