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