1use crate::{
2 db::{
3 DbSession, EntityFieldDescription, EntityResponse, EntitySchemaDescription,
4 MissingRowPolicy, PagedGroupedExecutionWithTrace, ProjectedRow, ProjectionResponse, Query,
5 QueryError,
6 query::{
7 builder::aggregate::{AggregateExpr, avg, count, count_by, max_by, min_by, sum},
8 intent::IntentError,
9 plan::{
10 AggregateKind, FieldSlot, QueryMode,
11 expr::{Expr, ProjectionField},
12 },
13 },
14 sql::lowering::{
15 PreparedSqlStatement as CorePreparedSqlStatement, SqlCommand,
16 SqlGlobalAggregateCommand, SqlGlobalAggregateTerminal, SqlLoweringError,
17 compile_sql_command, compile_sql_command_from_prepared_statement,
18 compile_sql_global_aggregate_command, prepare_sql_statement,
19 },
20 sql::parser::{SqlExplainMode, SqlExplainTarget, SqlStatement, parse_sql},
21 },
22 error::{ErrorClass, ErrorOrigin, InternalError},
23 traits::{CanisterKind, EntityKind, EntityValue},
24 value::Value,
25};
26
27#[derive(Clone, Debug, Eq, PartialEq)]
35pub enum SqlStatementRoute {
36 Query { entity: String },
37 Explain { entity: String },
38 Describe { entity: String },
39 ShowIndexes { entity: String },
40 ShowColumns { entity: String },
41 ShowEntities,
42}
43
44#[derive(Debug)]
50pub enum SqlDispatchResult<E: EntityKind> {
51 Projection {
52 columns: Vec<String>,
53 projection: ProjectionResponse<E>,
54 },
55 Explain(String),
56 Describe(EntitySchemaDescription),
57 ShowIndexes(Vec<String>),
58 ShowColumns(Vec<EntityFieldDescription>),
59 ShowEntities(Vec<String>),
60}
61
62#[derive(Clone, Debug)]
70pub struct SqlParsedStatement {
71 statement: SqlStatement,
72 route: SqlStatementRoute,
73}
74
75impl SqlParsedStatement {
76 #[must_use]
78 pub const fn route(&self) -> &SqlStatementRoute {
79 &self.route
80 }
81}
82
83#[derive(Clone, Debug)]
92pub struct SqlPreparedStatement {
93 prepared: CorePreparedSqlStatement,
94}
95
96impl SqlStatementRoute {
97 #[must_use]
102 pub const fn entity(&self) -> &str {
103 match self {
104 Self::Query { entity }
105 | Self::Explain { entity }
106 | Self::Describe { entity }
107 | Self::ShowIndexes { entity }
108 | Self::ShowColumns { entity } => entity.as_str(),
109 Self::ShowEntities => "",
110 }
111 }
112
113 #[must_use]
115 pub const fn is_explain(&self) -> bool {
116 matches!(self, Self::Explain { .. })
117 }
118
119 #[must_use]
121 pub const fn is_describe(&self) -> bool {
122 matches!(self, Self::Describe { .. })
123 }
124
125 #[must_use]
127 pub const fn is_show_indexes(&self) -> bool {
128 matches!(self, Self::ShowIndexes { .. })
129 }
130
131 #[must_use]
133 pub const fn is_show_columns(&self) -> bool {
134 matches!(self, Self::ShowColumns { .. })
135 }
136
137 #[must_use]
139 pub const fn is_show_entities(&self) -> bool {
140 matches!(self, Self::ShowEntities)
141 }
142}
143
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
146enum SqlLaneKind {
147 Query,
148 Explain,
149 Describe,
150 ShowIndexes,
151 ShowColumns,
152 ShowEntities,
153}
154
155#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157enum SqlSurface {
158 QueryFrom,
159 Explain,
160}
161
162const fn sql_command_lane<E: EntityKind>(command: &SqlCommand<E>) -> SqlLaneKind {
164 match command {
165 SqlCommand::Query(_) => SqlLaneKind::Query,
166 SqlCommand::Explain { .. } | SqlCommand::ExplainGlobalAggregate { .. } => {
167 SqlLaneKind::Explain
168 }
169 SqlCommand::DescribeEntity => SqlLaneKind::Describe,
170 SqlCommand::ShowIndexesEntity => SqlLaneKind::ShowIndexes,
171 SqlCommand::ShowColumnsEntity => SqlLaneKind::ShowColumns,
172 SqlCommand::ShowEntities => SqlLaneKind::ShowEntities,
173 }
174}
175
176const fn unsupported_sql_lane_message(surface: SqlSurface, lane: SqlLaneKind) -> &'static str {
178 match (surface, lane) {
179 (SqlSurface::QueryFrom, SqlLaneKind::Explain) => {
180 "query_from_sql does not accept EXPLAIN statements; use execute_sql_dispatch(...)"
181 }
182 (SqlSurface::QueryFrom, SqlLaneKind::Describe) => {
183 "query_from_sql does not accept DESCRIBE statements; use execute_sql_dispatch(...)"
184 }
185 (SqlSurface::QueryFrom, SqlLaneKind::ShowIndexes) => {
186 "query_from_sql does not accept SHOW INDEXES statements; use execute_sql_dispatch(...)"
187 }
188 (SqlSurface::QueryFrom, SqlLaneKind::ShowColumns) => {
189 "query_from_sql does not accept SHOW COLUMNS statements; use execute_sql_dispatch(...)"
190 }
191 (SqlSurface::QueryFrom, SqlLaneKind::ShowEntities) => {
192 "query_from_sql does not accept SHOW ENTITIES/SHOW TABLES statements; use execute_sql_dispatch(...)"
193 }
194 (SqlSurface::QueryFrom, SqlLaneKind::Query) => {
195 "query_from_sql requires one executable SELECT or DELETE statement"
196 }
197 (SqlSurface::Explain, SqlLaneKind::Describe) => {
198 "explain_sql does not accept DESCRIBE statements; use execute_sql_dispatch(...)"
199 }
200 (SqlSurface::Explain, SqlLaneKind::ShowIndexes) => {
201 "explain_sql does not accept SHOW INDEXES statements; use execute_sql_dispatch(...)"
202 }
203 (SqlSurface::Explain, SqlLaneKind::ShowColumns) => {
204 "explain_sql does not accept SHOW COLUMNS statements; use execute_sql_dispatch(...)"
205 }
206 (SqlSurface::Explain, SqlLaneKind::ShowEntities) => {
207 "explain_sql does not accept SHOW ENTITIES/SHOW TABLES statements; use execute_sql_dispatch(...)"
208 }
209 (SqlSurface::Explain, SqlLaneKind::Query | SqlLaneKind::Explain) => {
210 "explain_sql requires an EXPLAIN statement"
211 }
212 }
213}
214
215fn unsupported_sql_lane_error(surface: SqlSurface, lane: SqlLaneKind) -> QueryError {
217 QueryError::execute(InternalError::classified(
218 ErrorClass::Unsupported,
219 ErrorOrigin::Query,
220 unsupported_sql_lane_message(surface, lane),
221 ))
222}
223
224fn compile_sql_command_ignore<E: EntityKind>(sql: &str) -> Result<SqlCommand<E>, QueryError> {
226 compile_sql_command::<E>(sql, MissingRowPolicy::Ignore).map_err(map_sql_lowering_error)
227}
228
229fn map_sql_lowering_error(err: SqlLoweringError) -> QueryError {
231 match err {
232 SqlLoweringError::Query(err) => err,
233 SqlLoweringError::Parse(crate::db::sql::parser::SqlParseError::UnsupportedFeature {
234 feature,
235 }) => QueryError::execute(InternalError::query_unsupported_sql_feature(feature)),
236 other => QueryError::execute(InternalError::classified(
237 ErrorClass::Unsupported,
238 ErrorOrigin::Query,
239 format!("SQL query is not executable in this release: {other}"),
240 )),
241 }
242}
243
244fn map_sql_parse_error(err: crate::db::sql::parser::SqlParseError) -> QueryError {
247 map_sql_lowering_error(SqlLoweringError::Parse(err))
248}
249
250fn sql_statement_route_from_statement(statement: &SqlStatement) -> SqlStatementRoute {
252 match statement {
253 SqlStatement::Select(select) => SqlStatementRoute::Query {
254 entity: select.entity.clone(),
255 },
256 SqlStatement::Delete(delete) => SqlStatementRoute::Query {
257 entity: delete.entity.clone(),
258 },
259 SqlStatement::Explain(explain) => match &explain.statement {
260 SqlExplainTarget::Select(select) => SqlStatementRoute::Explain {
261 entity: select.entity.clone(),
262 },
263 SqlExplainTarget::Delete(delete) => SqlStatementRoute::Explain {
264 entity: delete.entity.clone(),
265 },
266 },
267 SqlStatement::Describe(describe) => SqlStatementRoute::Describe {
268 entity: describe.entity.clone(),
269 },
270 SqlStatement::ShowIndexes(show_indexes) => SqlStatementRoute::ShowIndexes {
271 entity: show_indexes.entity.clone(),
272 },
273 SqlStatement::ShowColumns(show_columns) => SqlStatementRoute::ShowColumns {
274 entity: show_columns.entity.clone(),
275 },
276 SqlStatement::ShowEntities(_) => SqlStatementRoute::ShowEntities,
277 }
278}
279
280fn resolve_sql_aggregate_target_slot<E: EntityKind>(field: &str) -> Result<FieldSlot, QueryError> {
283 FieldSlot::resolve(E::MODEL, field).ok_or_else(|| {
284 QueryError::execute(crate::db::error::executor_unsupported(format!(
285 "unknown aggregate target field: {field}",
286 )))
287 })
288}
289
290fn sql_global_aggregate_terminal_to_expr<E: EntityKind>(
293 terminal: &SqlGlobalAggregateTerminal,
294) -> Result<AggregateExpr, QueryError> {
295 match terminal {
296 SqlGlobalAggregateTerminal::CountRows => Ok(count()),
297 SqlGlobalAggregateTerminal::CountField(field) => {
298 let _ = resolve_sql_aggregate_target_slot::<E>(field)?;
299
300 Ok(count_by(field.as_str()))
301 }
302 SqlGlobalAggregateTerminal::SumField(field) => {
303 let _ = resolve_sql_aggregate_target_slot::<E>(field)?;
304
305 Ok(sum(field.as_str()))
306 }
307 SqlGlobalAggregateTerminal::AvgField(field) => {
308 let _ = resolve_sql_aggregate_target_slot::<E>(field)?;
309
310 Ok(avg(field.as_str()))
311 }
312 SqlGlobalAggregateTerminal::MinField(field) => {
313 let _ = resolve_sql_aggregate_target_slot::<E>(field)?;
314
315 Ok(min_by(field.as_str()))
316 }
317 SqlGlobalAggregateTerminal::MaxField(field) => {
318 let _ = resolve_sql_aggregate_target_slot::<E>(field)?;
319
320 Ok(max_by(field.as_str()))
321 }
322 }
323}
324
325fn projection_label_from_aggregate(aggregate: &AggregateExpr) -> String {
327 let kind = match aggregate.kind() {
328 AggregateKind::Count => "COUNT",
329 AggregateKind::Sum => "SUM",
330 AggregateKind::Avg => "AVG",
331 AggregateKind::Exists => "EXISTS",
332 AggregateKind::First => "FIRST",
333 AggregateKind::Last => "LAST",
334 AggregateKind::Min => "MIN",
335 AggregateKind::Max => "MAX",
336 };
337 let distinct = if aggregate.is_distinct() {
338 "DISTINCT "
339 } else {
340 ""
341 };
342
343 if let Some(field) = aggregate.target_field() {
344 return format!("{kind}({distinct}{field})");
345 }
346
347 format!("{kind}({distinct}*)")
348}
349
350fn projection_label_from_expr(expr: &Expr, ordinal: usize) -> String {
352 match expr {
353 Expr::Field(field) => field.as_str().to_string(),
354 Expr::Aggregate(aggregate) => projection_label_from_aggregate(aggregate),
355 Expr::Alias { name, .. } => name.as_str().to_string(),
356 Expr::Literal(_) | Expr::Unary { .. } | Expr::Binary { .. } => {
357 format!("expr_{ordinal}")
358 }
359 }
360}
361
362fn projection_labels_from_query<E: EntityKind>(
364 query: &Query<E>,
365) -> Result<Vec<String>, QueryError> {
366 let projection = query.plan()?.projection_spec();
367 let mut labels = Vec::with_capacity(projection.len());
368
369 for (ordinal, field) in projection.fields().enumerate() {
370 match field {
371 ProjectionField::Scalar {
372 expr: _,
373 alias: Some(alias),
374 } => labels.push(alias.as_str().to_string()),
375 ProjectionField::Scalar { expr, alias: None } => {
376 labels.push(projection_label_from_expr(expr, ordinal));
377 }
378 }
379 }
380
381 Ok(labels)
382}
383
384fn projection_labels_from_entity_model<E: EntityKind>() -> Vec<String> {
386 E::MODEL
387 .fields
388 .iter()
389 .map(|field| field.name.to_string())
390 .collect()
391}
392
393fn projection_from_entity_response<E>(response: EntityResponse<E>) -> ProjectionResponse<E>
397where
398 E: EntityKind + EntityValue,
399{
400 let projected = response
401 .rows()
402 .into_iter()
403 .map(|row| {
404 let (id, entity) = row.into_parts();
405 let values = (0..E::MODEL.fields.len())
406 .map(|index| entity.get_value_by_index(index).unwrap_or(Value::Null))
407 .collect();
408
409 ProjectedRow::new(id, values)
410 })
411 .collect();
412
413 ProjectionResponse::new(projected)
414}
415
416impl<C: CanisterKind> DbSession<C> {
417 fn execute_sql_dispatch_query_lane_from_command<E>(
419 &self,
420 command: SqlCommand<E>,
421 lane: SqlLaneKind,
422 ) -> Result<SqlDispatchResult<E>, QueryError>
423 where
424 E: EntityKind<Canister = C> + EntityValue,
425 {
426 match command {
427 SqlCommand::Query(query) => {
428 if query.has_grouping() {
429 return Err(QueryError::Intent(
430 IntentError::GroupedRequiresExecuteGrouped,
431 ));
432 }
433
434 match query.mode() {
435 QueryMode::Load(_) => {
436 let columns = projection_labels_from_query(&query)?;
437 let projection = self.execute_load_query_with(&query, |load, plan| {
438 load.execute_projection(plan)
439 })?;
440
441 Ok(SqlDispatchResult::Projection {
442 columns,
443 projection,
444 })
445 }
446 QueryMode::Delete(_) => {
447 let columns = projection_labels_from_entity_model::<E>();
448 let deleted = self.execute_query(&query)?;
449 let projection = projection_from_entity_response(deleted);
450
451 Ok(SqlDispatchResult::Projection {
452 columns,
453 projection,
454 })
455 }
456 }
457 }
458 SqlCommand::Explain { .. } | SqlCommand::ExplainGlobalAggregate { .. } => {
459 Self::explain_sql_from_command::<E>(command, lane).map(SqlDispatchResult::Explain)
460 }
461 SqlCommand::DescribeEntity
462 | SqlCommand::ShowIndexesEntity
463 | SqlCommand::ShowColumnsEntity
464 | SqlCommand::ShowEntities => Err(QueryError::execute(InternalError::classified(
465 ErrorClass::Unsupported,
466 ErrorOrigin::Query,
467 "query-lane SQL dispatch only accepts SELECT, DELETE, and EXPLAIN statements",
468 ))),
469 }
470 }
471
472 fn explain_sql_from_command<E>(
474 command: SqlCommand<E>,
475 lane: SqlLaneKind,
476 ) -> Result<String, QueryError>
477 where
478 E: EntityKind<Canister = C> + EntityValue,
479 {
480 match command {
481 SqlCommand::Query(_)
482 | SqlCommand::DescribeEntity
483 | SqlCommand::ShowIndexesEntity
484 | SqlCommand::ShowColumnsEntity
485 | SqlCommand::ShowEntities => {
486 Err(unsupported_sql_lane_error(SqlSurface::Explain, lane))
487 }
488 SqlCommand::Explain { mode, query } => match mode {
489 SqlExplainMode::Plan => Ok(query.explain()?.render_text_canonical()),
490 SqlExplainMode::Execution => query.explain_execution_text(),
491 SqlExplainMode::Json => Ok(query.explain()?.render_json_canonical()),
492 },
493 SqlCommand::ExplainGlobalAggregate { mode, command } => {
494 Self::explain_sql_global_aggregate::<E>(mode, command)
495 }
496 }
497 }
498
499 pub fn parse_sql_statement(&self, sql: &str) -> Result<SqlParsedStatement, QueryError> {
503 let statement = parse_sql(sql).map_err(map_sql_parse_error)?;
504 let route = sql_statement_route_from_statement(&statement);
505
506 Ok(SqlParsedStatement { statement, route })
507 }
508
509 pub fn sql_statement_route(&self, sql: &str) -> Result<SqlStatementRoute, QueryError> {
514 let parsed = self.parse_sql_statement(sql)?;
515
516 Ok(parsed.route().clone())
517 }
518
519 pub fn prepare_sql_dispatch_parsed(
524 &self,
525 parsed: &SqlParsedStatement,
526 expected_entity: &'static str,
527 ) -> Result<SqlPreparedStatement, QueryError> {
528 let prepared = prepare_sql_statement(parsed.statement.clone(), expected_entity)
529 .map_err(map_sql_lowering_error)?;
530
531 Ok(SqlPreparedStatement { prepared })
532 }
533
534 pub fn query_from_sql<E>(&self, sql: &str) -> Result<Query<E>, QueryError>
539 where
540 E: EntityKind<Canister = C>,
541 {
542 let command = compile_sql_command_ignore::<E>(sql)?;
543 let lane = sql_command_lane(&command);
544
545 match command {
546 SqlCommand::Query(query) => Ok(query),
547 SqlCommand::Explain { .. }
548 | SqlCommand::ExplainGlobalAggregate { .. }
549 | SqlCommand::DescribeEntity
550 | SqlCommand::ShowIndexesEntity
551 | SqlCommand::ShowColumnsEntity
552 | SqlCommand::ShowEntities => {
553 Err(unsupported_sql_lane_error(SqlSurface::QueryFrom, lane))
554 }
555 }
556 }
557
558 pub fn execute_sql<E>(&self, sql: &str) -> Result<EntityResponse<E>, QueryError>
560 where
561 E: EntityKind<Canister = C> + EntityValue,
562 {
563 let query = self.query_from_sql::<E>(sql)?;
564 if query.has_grouping() {
565 return Err(QueryError::Intent(
566 IntentError::GroupedRequiresExecuteGrouped,
567 ));
568 }
569
570 self.execute_query(&query)
571 }
572
573 pub fn execute_sql_aggregate<E>(&self, sql: &str) -> Result<Value, QueryError>
578 where
579 E: EntityKind<Canister = C> + EntityValue,
580 {
581 let command = compile_sql_global_aggregate_command::<E>(sql, MissingRowPolicy::Ignore)
582 .map_err(map_sql_lowering_error)?;
583
584 match command.terminal() {
585 SqlGlobalAggregateTerminal::CountRows => self
586 .execute_load_query_with(command.query(), |load, plan| load.aggregate_count(plan))
587 .map(|count| Value::Uint(u64::from(count))),
588 SqlGlobalAggregateTerminal::CountField(field) => {
589 let target_slot = resolve_sql_aggregate_target_slot::<E>(field)?;
590 self.execute_load_query_with(command.query(), |load, plan| {
591 load.values_by_slot(plan, target_slot)
592 })
593 .map(|values| {
594 let count = values
595 .into_iter()
596 .filter(|value| !matches!(value, Value::Null))
597 .count();
598 Value::Uint(u64::try_from(count).unwrap_or(u64::MAX))
599 })
600 }
601 SqlGlobalAggregateTerminal::SumField(field) => {
602 let target_slot = resolve_sql_aggregate_target_slot::<E>(field)?;
603 self.execute_load_query_with(command.query(), |load, plan| {
604 load.aggregate_sum_by_slot(plan, target_slot)
605 })
606 .map(|value| value.map_or(Value::Null, Value::Decimal))
607 }
608 SqlGlobalAggregateTerminal::AvgField(field) => {
609 let target_slot = resolve_sql_aggregate_target_slot::<E>(field)?;
610 self.execute_load_query_with(command.query(), |load, plan| {
611 load.aggregate_avg_by_slot(plan, target_slot)
612 })
613 .map(|value| value.map_or(Value::Null, Value::Decimal))
614 }
615 SqlGlobalAggregateTerminal::MinField(field) => {
616 let target_slot = resolve_sql_aggregate_target_slot::<E>(field)?;
617 let min_id = self.execute_load_query_with(command.query(), |load, plan| {
618 load.aggregate_min_by_slot(plan, target_slot)
619 })?;
620
621 match min_id {
622 Some(id) => self
623 .load::<E>()
624 .by_id(id)
625 .first_value_by(field)
626 .map(|value| value.unwrap_or(Value::Null)),
627 None => Ok(Value::Null),
628 }
629 }
630 SqlGlobalAggregateTerminal::MaxField(field) => {
631 let target_slot = resolve_sql_aggregate_target_slot::<E>(field)?;
632 let max_id = self.execute_load_query_with(command.query(), |load, plan| {
633 load.aggregate_max_by_slot(plan, target_slot)
634 })?;
635
636 match max_id {
637 Some(id) => self
638 .load::<E>()
639 .by_id(id)
640 .first_value_by(field)
641 .map(|value| value.unwrap_or(Value::Null)),
642 None => Ok(Value::Null),
643 }
644 }
645 }
646 }
647
648 pub fn execute_sql_grouped<E>(
650 &self,
651 sql: &str,
652 cursor_token: Option<&str>,
653 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
654 where
655 E: EntityKind<Canister = C> + EntityValue,
656 {
657 let query = self.query_from_sql::<E>(sql)?;
658 if !query.has_grouping() {
659 return Err(QueryError::execute(InternalError::classified(
660 ErrorClass::Unsupported,
661 ErrorOrigin::Query,
662 "execute_sql_grouped requires grouped SQL query intent",
663 )));
664 }
665
666 self.execute_grouped(&query, cursor_token)
667 }
668
669 pub fn execute_sql_dispatch<E>(&self, sql: &str) -> Result<SqlDispatchResult<E>, QueryError>
671 where
672 E: EntityKind<Canister = C> + EntityValue,
673 {
674 let parsed = self.parse_sql_statement(sql)?;
675
676 self.execute_sql_dispatch_parsed::<E>(&parsed)
677 }
678
679 pub fn execute_sql_dispatch_parsed<E>(
681 &self,
682 parsed: &SqlParsedStatement,
683 ) -> Result<SqlDispatchResult<E>, QueryError>
684 where
685 E: EntityKind<Canister = C> + EntityValue,
686 {
687 let prepared = self.prepare_sql_dispatch_parsed(parsed, E::MODEL.entity_name())?;
688
689 self.execute_sql_dispatch_prepared::<E>(&prepared)
690 }
691
692 pub fn execute_sql_dispatch_prepared<E>(
694 &self,
695 prepared: &SqlPreparedStatement,
696 ) -> Result<SqlDispatchResult<E>, QueryError>
697 where
698 E: EntityKind<Canister = C> + EntityValue,
699 {
700 let command = compile_sql_command_from_prepared_statement::<E>(
701 prepared.prepared.clone(),
702 MissingRowPolicy::Ignore,
703 )
704 .map_err(map_sql_lowering_error)?;
705 let lane = sql_command_lane(&command);
706
707 match command {
708 SqlCommand::Query(_)
709 | SqlCommand::Explain { .. }
710 | SqlCommand::ExplainGlobalAggregate { .. } => {
711 self.execute_sql_dispatch_query_lane_from_command::<E>(command, lane)
712 }
713 SqlCommand::DescribeEntity => {
714 Ok(SqlDispatchResult::Describe(self.describe_entity::<E>()))
715 }
716 SqlCommand::ShowIndexesEntity => {
717 Ok(SqlDispatchResult::ShowIndexes(self.show_indexes::<E>()))
718 }
719 SqlCommand::ShowColumnsEntity => {
720 Ok(SqlDispatchResult::ShowColumns(self.show_columns::<E>()))
721 }
722 SqlCommand::ShowEntities => Ok(SqlDispatchResult::ShowEntities(self.show_entities())),
723 }
724 }
725
726 pub fn execute_sql_dispatch_query_lane_prepared<E>(
728 &self,
729 prepared: &SqlPreparedStatement,
730 ) -> Result<SqlDispatchResult<E>, QueryError>
731 where
732 E: EntityKind<Canister = C> + EntityValue,
733 {
734 let command = compile_sql_command_from_prepared_statement::<E>(
735 prepared.prepared.clone(),
736 MissingRowPolicy::Ignore,
737 )
738 .map_err(map_sql_lowering_error)?;
739 let lane = sql_command_lane(&command);
740
741 self.execute_sql_dispatch_query_lane_from_command::<E>(command, lane)
742 }
743
744 fn explain_sql_global_aggregate<E>(
746 mode: SqlExplainMode,
747 command: SqlGlobalAggregateCommand<E>,
748 ) -> Result<String, QueryError>
749 where
750 E: EntityKind<Canister = C> + EntityValue,
751 {
752 match mode {
753 SqlExplainMode::Plan => {
754 let _ = sql_global_aggregate_terminal_to_expr::<E>(command.terminal())?;
757
758 Ok(command.query().explain()?.render_text_canonical())
759 }
760 SqlExplainMode::Execution => {
761 let aggregate = sql_global_aggregate_terminal_to_expr::<E>(command.terminal())?;
762 let plan = Self::explain_load_query_terminal_with(command.query(), aggregate)?;
763
764 Ok(plan.execution_node_descriptor().render_text_tree())
765 }
766 SqlExplainMode::Json => {
767 let _ = sql_global_aggregate_terminal_to_expr::<E>(command.terminal())?;
770
771 Ok(command.query().explain()?.render_json_canonical())
772 }
773 }
774 }
775}