1mod aggregate;
8mod computed_projection;
9mod dispatch;
10mod explain;
11mod projection;
12mod surface;
13
14use crate::{
15 db::{
16 DbSession, EntityResponse, GroupedTextCursorPageWithTrace, MissingRowPolicy,
17 PagedGroupedExecutionWithTrace, PersistedRow, Query, QueryError,
18 executor::EntityAuthority,
19 identifiers_tail_match,
20 query::{
21 intent::StructuralQuery,
22 plan::{AccessPlannedQuery, VisibleIndexes},
23 },
24 sql::{
25 lowering::{
26 bind_lowered_sql_query, lower_sql_command_from_prepared_statement,
27 prepare_sql_statement,
28 },
29 parser::{SqlStatement, parse_sql},
30 },
31 },
32 traits::{CanisterKind, EntityKind, EntityValue},
33};
34
35use crate::db::session::sql::aggregate::{
36 SqlAggregateSurface, parsed_requires_dedicated_sql_aggregate_lane,
37 unsupported_sql_aggregate_lane_message,
38};
39use crate::db::session::sql::surface::{
40 SqlSurface, session_sql_lane, sql_statement_route_from_statement, unsupported_sql_lane_message,
41};
42
43#[cfg(feature = "structural-read-metrics")]
44pub use crate::db::session::sql::projection::{
45 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
46};
47pub use crate::db::session::sql::surface::{
48 SqlDispatchResult, SqlParsedStatement, SqlStatementRoute,
49};
50#[cfg(feature = "perf-attribution")]
51pub use crate::db::{
52 session::sql::dispatch::LoweredSqlDispatchExecutorAttribution,
53 session::sql::projection::SqlProjectionTextExecutorAttribution,
54};
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57enum SqlComputedProjectionSurface {
58 QueryFrom,
59 ExecuteSql,
60 ExecuteSqlGrouped,
61}
62
63const fn unsupported_sql_computed_projection_message(
64 surface: SqlComputedProjectionSurface,
65) -> &'static str {
66 match surface {
67 SqlComputedProjectionSurface::QueryFrom => {
68 "query_from_sql does not accept computed text projection"
69 }
70 SqlComputedProjectionSurface::ExecuteSql => "execute_sql rejects computed text projection",
71 SqlComputedProjectionSurface::ExecuteSqlGrouped => {
72 "execute_sql_grouped rejects scalar computed text projection"
73 }
74 }
75}
76
77const fn unsupported_sql_write_surface_message(
78 surface: SqlSurface,
79 statement: &SqlStatement,
80) -> &'static str {
81 match (surface, statement) {
82 (SqlSurface::QueryFrom, SqlStatement::Insert(_)) => {
83 "query_from_sql rejects INSERT; use create(...) or insert(...)"
84 }
85 (SqlSurface::QueryFrom, SqlStatement::Update(_)) => {
86 "query_from_sql rejects UPDATE; use update(...)"
87 }
88 (SqlSurface::ExecuteSql, SqlStatement::Insert(_)) => {
89 "execute_sql rejects INSERT; use create(...) or insert(...)"
90 }
91 (SqlSurface::ExecuteSql, SqlStatement::Update(_)) => {
92 "execute_sql rejects UPDATE; use update(...)"
93 }
94 (SqlSurface::ExecuteSqlGrouped, SqlStatement::Insert(_)) => {
95 "execute_sql_grouped rejects INSERT; use create(...) or insert(...)"
96 }
97 (SqlSurface::ExecuteSqlGrouped, SqlStatement::Update(_)) => {
98 "execute_sql_grouped rejects UPDATE; use update(...)"
99 }
100 (SqlSurface::Explain, SqlStatement::Insert(_) | SqlStatement::Update(_)) => {
101 "explain_sql requires EXPLAIN"
102 }
103 (
104 _,
105 SqlStatement::Select(_)
106 | SqlStatement::Delete(_)
107 | SqlStatement::Explain(_)
108 | SqlStatement::Describe(_)
109 | SqlStatement::ShowIndexes(_)
110 | SqlStatement::ShowColumns(_)
111 | SqlStatement::ShowEntities(_),
112 ) => unreachable!(),
113 }
114}
115
116const fn unsupported_sql_returning_surface_message(
117 surface: SqlSurface,
118 statement: &SqlStatement,
119) -> &'static str {
120 match (surface, statement) {
121 (SqlSurface::QueryFrom, SqlStatement::Delete(_)) => {
122 "query_from_sql rejects DELETE RETURNING; use delete::<E>().returning..."
123 }
124 (SqlSurface::ExecuteSql, SqlStatement::Delete(_)) => {
125 "execute_sql rejects DELETE RETURNING; use delete::<E>().returning..."
126 }
127 (SqlSurface::ExecuteSqlGrouped, SqlStatement::Delete(_)) => {
128 "execute_sql_grouped rejects DELETE RETURNING; use delete::<E>().returning..."
129 }
130 (SqlSurface::Explain, SqlStatement::Delete(_)) => "explain_sql requires EXPLAIN",
131 (
132 _,
133 SqlStatement::Select(_)
134 | SqlStatement::Insert(_)
135 | SqlStatement::Update(_)
136 | SqlStatement::Explain(_)
137 | SqlStatement::Describe(_)
138 | SqlStatement::ShowIndexes(_)
139 | SqlStatement::ShowColumns(_)
140 | SqlStatement::ShowEntities(_),
141 ) => unreachable!(),
142 }
143}
144
145impl<C: CanisterKind> DbSession<C> {
146 fn ensure_entity_sql_route_matches<E>(route: &SqlStatementRoute) -> Result<(), QueryError>
149 where
150 E: EntityKind<Canister = C>,
151 {
152 let Some(sql_entity) = (match route {
153 SqlStatementRoute::Query { entity }
154 | SqlStatementRoute::Insert { entity }
155 | SqlStatementRoute::Update { entity }
156 | SqlStatementRoute::Explain { entity }
157 | SqlStatementRoute::Describe { entity }
158 | SqlStatementRoute::ShowIndexes { entity }
159 | SqlStatementRoute::ShowColumns { entity } => Some(entity.as_str()),
160 SqlStatementRoute::ShowEntities => None,
161 }) else {
162 return Ok(());
163 };
164
165 if identifiers_tail_match(sql_entity, E::MODEL.name()) {
166 return Ok(());
167 }
168
169 Err(QueryError::unsupported_query(format!(
170 "execute_entity_sql only supports entity '{}', but received '{sql_entity}'",
171 E::MODEL.name()
172 )))
173 }
174
175 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
178 &self,
179 query: StructuralQuery,
180 authority: EntityAuthority,
181 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
182 let visible_indexes =
183 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
184 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
185
186 Ok((visible_indexes, plan))
187 }
188
189 fn query_from_sql_parsed<E>(
192 parsed: &SqlParsedStatement,
193 lane_surface: SqlSurface,
194 computed_surface: SqlComputedProjectionSurface,
195 surface: SqlAggregateSurface,
196 ) -> Result<Query<E>, QueryError>
197 where
198 E: EntityKind<Canister = C>,
199 {
200 if matches!(
201 &parsed.statement,
202 SqlStatement::Insert(_) | SqlStatement::Update(_)
203 ) {
204 return Err(QueryError::unsupported_query(
205 unsupported_sql_write_surface_message(lane_surface, &parsed.statement),
206 ));
207 }
208 if matches!(&parsed.statement, SqlStatement::Delete(delete) if delete.returning.is_some()) {
209 return Err(QueryError::unsupported_query(
210 unsupported_sql_returning_surface_message(lane_surface, &parsed.statement),
211 ));
212 }
213
214 if computed_projection::computed_sql_projection_plan(&parsed.statement)?.is_some() {
215 return Err(QueryError::unsupported_query(
216 unsupported_sql_computed_projection_message(computed_surface),
217 ));
218 }
219
220 if parsed_requires_dedicated_sql_aggregate_lane(parsed) {
221 return Err(QueryError::unsupported_query(
222 unsupported_sql_aggregate_lane_message(surface),
223 ));
224 }
225
226 let lowered = lower_sql_command_from_prepared_statement(
227 parsed.prepare(E::MODEL.name())?,
228 E::MODEL.primary_key.name,
229 )
230 .map_err(QueryError::from_sql_lowering_error)?;
231 let lane = session_sql_lane(&lowered);
232 let Some(query) = lowered.query().cloned() else {
233 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
234 lane_surface,
235 lane,
236 )));
237 };
238 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
239 .map_err(QueryError::from_sql_lowering_error)?;
240
241 Ok(query)
242 }
243
244 fn grouped_query_from_computed_sql_projection_plan<E>(
247 plan: &computed_projection::SqlComputedProjectionPlan,
248 ) -> Result<Query<E>, QueryError>
249 where
250 E: EntityKind<Canister = C>,
251 {
252 let lowered = lower_sql_command_from_prepared_statement(
253 prepare_sql_statement(plan.cloned_base_statement(), E::MODEL.name())
254 .map_err(QueryError::from_sql_lowering_error)?,
255 E::MODEL.primary_key.name,
256 )
257 .map_err(QueryError::from_sql_lowering_error)?;
258 let Some(query) = lowered.query().cloned() else {
259 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
260 SqlSurface::ExecuteSqlGrouped,
261 session_sql_lane(&lowered),
262 )));
263 };
264 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
265 .map_err(QueryError::from_sql_lowering_error)?;
266 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
267
268 Ok(query)
269 }
270
271 pub fn parse_sql_statement(&self, sql: &str) -> Result<SqlParsedStatement, QueryError> {
275 let statement = parse_sql(sql).map_err(QueryError::from_sql_parse_error)?;
276 let route = sql_statement_route_from_statement(&statement);
277
278 Ok(SqlParsedStatement::new(statement, route))
279 }
280
281 pub fn sql_statement_route(&self, sql: &str) -> Result<SqlStatementRoute, QueryError> {
286 let parsed = self.parse_sql_statement(sql)?;
287
288 Ok(parsed.route().clone())
289 }
290
291 pub fn query_from_sql<E>(&self, sql: &str) -> Result<Query<E>, QueryError>
296 where
297 E: EntityKind<Canister = C>,
298 {
299 let parsed = self.parse_sql_statement(sql)?;
300
301 Self::query_from_sql_parsed::<E>(
302 &parsed,
303 SqlSurface::QueryFrom,
304 SqlComputedProjectionSurface::QueryFrom,
305 SqlAggregateSurface::QueryFrom,
306 )
307 }
308
309 pub fn execute_sql<E>(&self, sql: &str) -> Result<EntityResponse<E>, QueryError>
311 where
312 E: PersistedRow<Canister = C> + EntityValue,
313 {
314 let parsed = self.parse_sql_statement(sql)?;
315 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
316 return Err(QueryError::unsupported_query(
317 "execute_sql rejects DELETE; use delete::<E>()",
318 ));
319 }
320 let query = Self::query_from_sql_parsed::<E>(
321 &parsed,
322 SqlSurface::ExecuteSql,
323 SqlComputedProjectionSurface::ExecuteSql,
324 SqlAggregateSurface::ExecuteSql,
325 )?;
326 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Scalar)?;
327
328 self.execute_query(&query)
329 }
330
331 pub fn execute_entity_sql<E>(&self, sql: &str) -> Result<SqlDispatchResult, QueryError>
337 where
338 E: PersistedRow<Canister = C> + EntityValue,
339 {
340 let parsed = self.parse_sql_statement(sql)?;
341
342 Self::ensure_entity_sql_route_matches::<E>(parsed.route())?;
343
344 self.execute_sql_dispatch_parsed::<E>(&parsed)
345 }
346
347 pub fn execute_sql_grouped<E>(
349 &self,
350 sql: &str,
351 cursor_token: Option<&str>,
352 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
353 where
354 E: PersistedRow<Canister = C> + EntityValue,
355 {
356 let parsed = self.parse_sql_statement(sql)?;
357
358 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
359 return Err(QueryError::unsupported_query(
360 "execute_sql_grouped rejects DELETE; use delete::<E>()",
361 ));
362 }
363
364 if let Some(plan) = computed_projection::computed_sql_projection_plan(&parsed.statement)? {
365 if !plan.is_grouped() {
366 return Err(QueryError::unsupported_query(
367 unsupported_sql_computed_projection_message(
368 SqlComputedProjectionSurface::ExecuteSqlGrouped,
369 ),
370 ));
371 }
372
373 let query = Self::grouped_query_from_computed_sql_projection_plan::<E>(&plan)?;
374 let grouped = self.execute_grouped(&query, cursor_token)?;
375 let (rows, continuation_cursor, execution_trace) = grouped.into_parts();
376 let rows =
377 computed_projection::apply_computed_sql_projection_grouped_rows(rows, &plan)?;
378
379 return Ok(PagedGroupedExecutionWithTrace::new(
380 rows,
381 continuation_cursor,
382 execution_trace,
383 ));
384 }
385
386 let query = Self::query_from_sql_parsed::<E>(
387 &parsed,
388 SqlSurface::ExecuteSqlGrouped,
389 SqlComputedProjectionSurface::ExecuteSqlGrouped,
390 SqlAggregateSurface::ExecuteSqlGrouped,
391 )?;
392 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
393
394 self.execute_grouped(&query, cursor_token)
395 }
396
397 #[doc(hidden)]
399 pub fn execute_sql_grouped_text_cursor<E>(
400 &self,
401 sql: &str,
402 cursor_token: Option<&str>,
403 ) -> Result<GroupedTextCursorPageWithTrace, QueryError>
404 where
405 E: PersistedRow<Canister = C> + EntityValue,
406 {
407 let parsed = self.parse_sql_statement(sql)?;
408
409 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
410 return Err(QueryError::unsupported_query(
411 "execute_sql_grouped rejects DELETE; use delete::<E>()",
412 ));
413 }
414
415 if let Some(plan) = computed_projection::computed_sql_projection_plan(&parsed.statement)? {
416 if !plan.is_grouped() {
417 return Err(QueryError::unsupported_query(
418 unsupported_sql_computed_projection_message(
419 SqlComputedProjectionSurface::ExecuteSqlGrouped,
420 ),
421 ));
422 }
423
424 let query = Self::grouped_query_from_computed_sql_projection_plan::<E>(&plan)?;
425 let (rows, continuation_cursor, execution_trace) =
426 self.execute_grouped_text_cursor(&query, cursor_token)?;
427 let rows =
428 computed_projection::apply_computed_sql_projection_grouped_rows(rows, &plan)?;
429
430 return Ok((rows, continuation_cursor, execution_trace));
431 }
432
433 let query = Self::query_from_sql_parsed::<E>(
434 &parsed,
435 SqlSurface::ExecuteSqlGrouped,
436 SqlComputedProjectionSurface::ExecuteSqlGrouped,
437 SqlAggregateSurface::ExecuteSqlGrouped,
438 )?;
439 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
440
441 self.execute_grouped_text_cursor(&query, cursor_token)
442 }
443}