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