Skip to main content

uqa_sql/plan/
statement.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Top-level SQL statement lowering and command naming.
8
9use super::model::NoRegisteredAggregates;
10use super::query::{lower_assignments, lower_ctes, lower_merge_when};
11use super::rewrite::{rewrite_command_scalars, rewrite_query_scalars};
12use super::scalar::lower_scalar_expression;
13use super::{
14    AggregateClassifier, CommandPlan, ConflictActionPlan, ConflictPlan, DeletePlan, ExpressionPlan,
15    InsertPlan, MergePlan, ProjectionPlan, QueryPlan, RelationalPlan, ScalarExpr, SourcePlan,
16    Statement, UnifiedPlan, UpdatePlan,
17};
18
19impl UnifiedPlan {
20    /// Lower a statement using only the built-in SQL aggregate catalogue.
21    #[must_use]
22    pub fn lower(statement: Statement) -> Self {
23        Self::lower_with(statement, &NoRegisteredAggregates)
24    }
25
26    /// Lower a statement with engine-local aggregate classification.
27    #[must_use]
28    #[expect(
29        clippy::too_many_lines,
30        reason = "plan lowering preserves exhaustive variants and structural identities"
31    )]
32    pub fn lower_with(statement: Statement, aggregates: &dyn AggregateClassifier) -> Self {
33        match statement {
34            Statement::Select(query) => {
35                Self::Query(Box::new(QueryPlan::lower_with(*query, aggregates)))
36            }
37            Statement::Values { rows } => {
38                let mut subqueries = Vec::new();
39                let rows = rows
40                    .into_iter()
41                    .map(|row| {
42                        row.into_iter()
43                            .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
44                            .collect()
45                    })
46                    .collect();
47                Self::Query(Box::new(QueryPlan {
48                    relations_bound: false,
49                    ctes: Vec::new(),
50                    root: RelationalPlan::Values { rows, subqueries },
51                }))
52            }
53            Statement::CreateTable(value) => {
54                Self::Command(Box::new(CommandPlan::CreateTable(Box::new(value))))
55            }
56            Statement::CreateTableIfNotExists(value) => {
57                Self::Command(Box::new(CommandPlan::CreateTableIfNotExists(value)))
58            }
59            Statement::CreateIndex(value) => {
60                Self::Command(Box::new(CommandPlan::CreateIndex(value)))
61            }
62            Statement::Insert(statement) => {
63                let ctes = lower_ctes(&statement.with, aggregates);
64                let source = statement
65                    .select_source
66                    .map(|query| Box::new(QueryPlan::lower_with(*query, aggregates)));
67                let mut subqueries = Vec::new();
68                let rows = statement
69                    .rows
70                    .into_iter()
71                    .map(|row| {
72                        row.into_iter()
73                            .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
74                            .collect()
75                    })
76                    .collect();
77                let on_conflict = statement.on_conflict.map(|conflict| {
78                    let action = match conflict.action {
79                        crate::ast::OnConflictAction::Nothing => ConflictActionPlan::Nothing,
80                        crate::ast::OnConflictAction::Update {
81                            assignments,
82                            r#where,
83                        } => ConflictActionPlan::Update {
84                            assignments: lower_assignments(
85                                assignments,
86                                aggregates,
87                                &mut subqueries,
88                            ),
89                            predicate: r#where.map(|expr| {
90                                Box::new(lower_scalar_expression(
91                                    *expr,
92                                    aggregates,
93                                    &mut subqueries,
94                                ))
95                            }),
96                        },
97                    };
98                    ConflictPlan {
99                        predicate: conflict.predicate.map(|expr| {
100                            Box::new(lower_scalar_expression(*expr, aggregates, &mut subqueries))
101                        }),
102                        constraint: conflict.constraint,
103                        conflict_columns: conflict.conflict_columns,
104                        expressions: conflict
105                            .expressions
106                            .into_iter()
107                            .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
108                            .collect(),
109                        action,
110                    }
111                });
112                let returning = statement
113                    .returning
114                    .into_iter()
115                    .map(|projection| {
116                        ProjectionPlan::lower_with(projection, aggregates, &mut subqueries)
117                    })
118                    .collect();
119                Self::Command(Box::new(CommandPlan::Insert(Box::new(InsertPlan {
120                    table: statement.table,
121                    target_relation_bound: statement.target_relation_bound,
122                    relations_bound: false,
123                    statement_privilege_subject: None,
124                    target_privilege_subject: None,
125                    target_qualifier: statement.target_qualifier,
126                    include_descendants: statement.include_descendants,
127                    columns: statement.columns,
128                    ctes,
129                    rows,
130                    source,
131                    on_conflict,
132                    returning,
133                    returning_aliases: statement.returning_aliases,
134                    subqueries,
135                    view_checks: Vec::new(),
136                    view_rule_relations: Vec::new(),
137                    view_rule_insert_plans: Vec::new(),
138                    view_rule_returning: None,
139                }))))
140            }
141            Statement::Update(statement) => {
142                let ctes = lower_ctes(&statement.with, aggregates);
143                let mut subqueries = Vec::new();
144                let source = statement
145                    .from
146                    .map(|from| SourcePlan::lower_with(from, aggregates, &mut subqueries));
147                let assignments =
148                    lower_assignments(statement.assignments, aggregates, &mut subqueries);
149                let predicate = statement
150                    .r#where
151                    .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries));
152                let returning = statement
153                    .returning
154                    .into_iter()
155                    .map(|projection| {
156                        ProjectionPlan::lower_with(projection, aggregates, &mut subqueries)
157                    })
158                    .collect();
159                Self::Command(Box::new(CommandPlan::Update(Box::new(UpdatePlan {
160                    table: statement.table,
161                    target_relation_bound: statement.target_relation_bound,
162                    relations_bound: false,
163                    statement_privilege_subject: None,
164                    target_privilege_subject: None,
165                    target_qualifier: statement.target_qualifier,
166                    include_descendants: statement.include_descendants,
167                    assignments,
168                    predicate,
169                    ctes,
170                    source: source.map(Box::new),
171                    returning,
172                    returning_aliases: statement.returning_aliases,
173                    subqueries,
174                    view_checks: Vec::new(),
175                    view_rule_relations: Vec::new(),
176                    view_rule_update_plans: Vec::new(),
177                    view_rule_returning: None,
178                }))))
179            }
180            Statement::Delete(statement) => {
181                let ctes = lower_ctes(&statement.with, aggregates);
182                let mut subqueries = Vec::new();
183                let source = statement
184                    .using
185                    .map(|from| SourcePlan::lower_with(from, aggregates, &mut subqueries));
186                let predicate = statement
187                    .r#where
188                    .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries));
189                let returning = statement
190                    .returning
191                    .into_iter()
192                    .map(|projection| {
193                        ProjectionPlan::lower_with(projection, aggregates, &mut subqueries)
194                    })
195                    .collect();
196                Self::Command(Box::new(CommandPlan::Delete(Box::new(DeletePlan {
197                    table: statement.table,
198                    target_relation_bound: statement.target_relation_bound,
199                    relations_bound: false,
200                    statement_privilege_subject: None,
201                    target_privilege_subject: None,
202                    target_qualifier: statement.target_qualifier,
203                    include_descendants: statement.include_descendants,
204                    predicate,
205                    ctes,
206                    source: source.map(Box::new),
207                    returning,
208                    returning_aliases: statement.returning_aliases,
209                    subqueries,
210                    view_rule_relations: Vec::new(),
211                    view_rule_returning: None,
212                }))))
213            }
214            Statement::Drop(value) => Self::Command(Box::new(CommandPlan::Drop(value))),
215            Statement::AlterTable(value) => {
216                Self::Command(Box::new(CommandPlan::AlterTable(Box::new(value))))
217            }
218            Statement::AlterForeignTable(value) => {
219                Self::Command(Box::new(CommandPlan::AlterForeignTable(value)))
220            }
221            Statement::AlterView(value) => Self::Command(Box::new(CommandPlan::AlterView(value))),
222            Statement::CreateView {
223                name,
224                column_names,
225                body,
226                or_replace,
227                persistence,
228                options,
229            } => {
230                let query = Box::new(QueryPlan::lower_with(*body, aggregates));
231                Self::Command(Box::new(CommandPlan::CreateView {
232                    name,
233                    column_names,
234                    query,
235                    or_replace,
236                    persistence,
237                    options,
238                }))
239            }
240            Statement::CreateMaterializedView {
241                name,
242                column_names,
243                if_not_exists,
244                with_no_data,
245                options,
246                body,
247            } => Self::Command(Box::new(CommandPlan::CreateMaterializedView {
248                name,
249                column_names,
250                if_not_exists,
251                with_no_data,
252                options,
253                query: Box::new(QueryPlan::lower_with(*body, aggregates)),
254            })),
255            Statement::RefreshMaterializedView {
256                name,
257                concurrently,
258                with_no_data,
259            } => Self::Command(Box::new(CommandPlan::RefreshMaterializedView {
260                name,
261                concurrently,
262                with_no_data,
263            })),
264            Statement::CreateSchema {
265                name,
266                if_not_exists,
267                authorization,
268            } => Self::Command(Box::new(CommandPlan::CreateSchema {
269                name,
270                if_not_exists,
271                authorization,
272            })),
273            Statement::AlterSchemaOwner { name, new_owner } => {
274                Self::Command(Box::new(CommandPlan::AlterSchemaOwner { name, new_owner }))
275            }
276            Statement::Notify { channel, payload } => {
277                Self::Command(Box::new(CommandPlan::Notify { channel, payload }))
278            }
279            Statement::Listen { channel } => {
280                Self::Command(Box::new(CommandPlan::Listen { channel }))
281            }
282            Statement::Unlisten { channel } => {
283                Self::Command(Box::new(CommandPlan::Unlisten { channel }))
284            }
285            Statement::SetVariable {
286                name,
287                value,
288                local,
289                is_default,
290            } => Self::Command(Box::new(CommandPlan::SetVariable {
291                name,
292                value,
293                local,
294                is_default,
295            })),
296            Statement::ResetVariable { name } => {
297                Self::Command(Box::new(CommandPlan::ResetVariable { name }))
298            }
299            Statement::ResetAllVariables => Self::Command(Box::new(CommandPlan::ResetAllVariables)),
300            Statement::SetConstraints {
301                constraints,
302                deferred,
303            } => Self::Command(Box::new(CommandPlan::SetConstraints {
304                constraints,
305                deferred,
306            })),
307            Statement::ShowVariable { name } => {
308                Self::Command(Box::new(CommandPlan::ShowVariable { name }))
309            }
310            Statement::Discard { target } => {
311                Self::Command(Box::new(CommandPlan::Discard { target }))
312            }
313            Statement::Load { library } => Self::Command(Box::new(CommandPlan::Load { library })),
314            Statement::Explain {
315                analyze,
316                verbose,
317                format,
318                body,
319            } => Self::Command(Box::new(CommandPlan::Explain {
320                analyze,
321                verbose,
322                format,
323                body: Box::new(Self::lower_with(*body, aggregates)),
324            })),
325            Statement::Analyze { table } => Self::Command(Box::new(CommandPlan::Analyze { table })),
326            Statement::Vacuum(vacuum) => Self::Command(Box::new(CommandPlan::Vacuum(vacuum))),
327            Statement::Truncate {
328                tables,
329                cascade,
330                restart_identity,
331            } => Self::Command(Box::new(CommandPlan::Truncate {
332                tables,
333                cascade,
334                restart_identity,
335            })),
336            Statement::Transaction(value) => {
337                Self::Command(Box::new(CommandPlan::Transaction(value)))
338            }
339            Statement::DeclareCursor(cursor) => {
340                Self::Command(Box::new(CommandPlan::DeclareCursor {
341                    name: cursor.name,
342                    binary: cursor.binary,
343                    scroll: cursor.scroll,
344                    hold: cursor.hold,
345                    query: Box::new(QueryPlan::lower_with(*cursor.query, aggregates)),
346                }))
347            }
348            Statement::FetchCursor(cursor) => {
349                Self::Command(Box::new(CommandPlan::FetchCursor(cursor)))
350            }
351            Statement::CloseCursor { name } => {
352                Self::Command(Box::new(CommandPlan::CloseCursor { name }))
353            }
354            Statement::CreateSequence(value) => {
355                Self::Command(Box::new(CommandPlan::CreateSequence(value)))
356            }
357            Statement::CreateDomain(value) => {
358                Self::Command(Box::new(CommandPlan::CreateDomain(value)))
359            }
360            Statement::AlterSequence(value) => {
361                Self::Command(Box::new(CommandPlan::AlterSequence(value)))
362            }
363            Statement::CreateTableAs {
364                name,
365                if_not_exists,
366                column_names,
367                with_no_data,
368                persistence,
369                on_commit,
370                body,
371            } => Self::Command(Box::new(CommandPlan::CreateTableAs {
372                name,
373                if_not_exists,
374                column_names,
375                with_no_data,
376                persistence,
377                on_commit,
378                query: Box::new(QueryPlan::lower_with(*body, aggregates)),
379            })),
380            Statement::Prepare {
381                name,
382                parameter_types,
383                body,
384            } => {
385                let body = Box::new(Self::lower_with(*body, aggregates));
386                Self::Command(Box::new(CommandPlan::Prepare {
387                    name,
388                    parameter_types,
389                    body,
390                }))
391            }
392            Statement::Execute { name, params } => Self::Command(Box::new(CommandPlan::Execute {
393                name,
394                params: params
395                    .into_iter()
396                    .map(|expr| ExpressionPlan::lower_with(expr, aggregates))
397                    .collect(),
398            })),
399            Statement::Deallocate { name } => {
400                Self::Command(Box::new(CommandPlan::Deallocate { name }))
401            }
402            Statement::CreateForeignServer(value) => {
403                Self::Command(Box::new(CommandPlan::CreateForeignServer(value)))
404            }
405            Statement::CreateForeignTable(value) => {
406                Self::Command(Box::new(CommandPlan::CreateForeignTable(value)))
407            }
408            Statement::CreateForeignTableIfNotExists(value) => {
409                Self::Command(Box::new(CommandPlan::CreateForeignTableIfNotExists(value)))
410            }
411            Statement::Merge(statement) => {
412                let mut subqueries = Vec::new();
413                let source = SourcePlan::lower_with(statement.source, aggregates, &mut subqueries);
414                let join_condition =
415                    lower_scalar_expression(statement.join_condition, aggregates, &mut subqueries);
416                let when_clauses = statement
417                    .when_clauses
418                    .into_iter()
419                    .map(|clause| lower_merge_when(clause, aggregates, &mut subqueries))
420                    .collect();
421                let returning = statement
422                    .returning
423                    .into_iter()
424                    .map(|projection| {
425                        ProjectionPlan::lower_with(projection, aggregates, &mut subqueries)
426                    })
427                    .collect();
428                Self::Command(Box::new(CommandPlan::Merge(Box::new(MergePlan {
429                    ctes: lower_ctes(&statement.with, aggregates),
430                    target: statement.target,
431                    statement_privilege_subject: None,
432                    target_privilege_subject: None,
433                    target_qualifier: statement.target_qualifier,
434                    target_alias: statement.target_alias,
435                    include_descendants: statement.include_descendants,
436                    target_predicate: None,
437                    source: Box::new(source),
438                    join_condition,
439                    when_clauses,
440                    returning,
441                    returning_aliases: statement.returning_aliases,
442                    subqueries,
443                    view_checks: Vec::new(),
444                }))))
445            }
446            Statement::CreateFunction(value) => {
447                Self::Command(Box::new(CommandPlan::CreateFunction(value)))
448            }
449            Statement::DropFunction(value) => {
450                Self::Command(Box::new(CommandPlan::DropFunction(value)))
451            }
452            Statement::AlterRoutine(value) => {
453                Self::Command(Box::new(CommandPlan::AlterRoutine(value)))
454            }
455            Statement::AlterRoutineOwner(value) => {
456                Self::Command(Box::new(CommandPlan::AlterRoutineOwner(value)))
457            }
458            Statement::RenameRoutine(value) => {
459                Self::Command(Box::new(CommandPlan::RenameRoutine(value)))
460            }
461            Statement::GrantRoutine(value) => {
462                Self::Command(Box::new(CommandPlan::GrantRoutine(value)))
463            }
464            Statement::GrantTable(value) => Self::Command(Box::new(CommandPlan::GrantTable(value))),
465            Statement::GrantSequence(value) => {
466                Self::Command(Box::new(CommandPlan::GrantSequence(value)))
467            }
468            Statement::GrantDatabase(value) => {
469                Self::Command(Box::new(CommandPlan::GrantDatabase(value)))
470            }
471            Statement::GrantSchema(value) => {
472                Self::Command(Box::new(CommandPlan::GrantSchema(value)))
473            }
474            Statement::GrantRole(value) => Self::Command(Box::new(CommandPlan::GrantRole(value))),
475            Statement::CreateRole(value) => Self::Command(Box::new(CommandPlan::CreateRole(value))),
476            Statement::AlterRole(value) => Self::Command(Box::new(CommandPlan::AlterRole(value))),
477            Statement::DropRole(value) => Self::Command(Box::new(CommandPlan::DropRole(value))),
478            Statement::CreateTrigger(value) => {
479                Self::Command(Box::new(CommandPlan::CreateTrigger(value)))
480            }
481            Statement::DropTrigger(value) => {
482                Self::Command(Box::new(CommandPlan::DropTrigger(value)))
483            }
484            Statement::CreateRule(value) => Self::Command(Box::new(CommandPlan::CreateRule(value))),
485            Statement::DropRule(value) => Self::Command(Box::new(CommandPlan::DropRule(value))),
486            Statement::DoBlock { language, body } => {
487                Self::Command(Box::new(CommandPlan::DoBlock { language, body }))
488            }
489            Statement::Call { name, args } => Self::Command(Box::new(CommandPlan::Call {
490                name,
491                args: args
492                    .into_iter()
493                    .map(|expr| ExpressionPlan::lower_with(expr, aggregates))
494                    .collect(),
495            })),
496        }
497    }
498
499    #[must_use]
500    pub fn name(&self) -> &'static str {
501        match self {
502            Self::Query(_) => "Query",
503            Self::Command(command) => command.name(),
504        }
505    }
506
507    /// Rewrite every physical scalar slot owned by this plan, including
508    /// CTEs, scalar subqueries, mutation sources, prepared/explained bodies,
509    /// and routine-call arguments.  This is the plan-native binding hook used
510    /// by SQL-language routines; callers never need to reconstruct an AST to
511    /// specialize a stored plan.
512    pub fn rewrite_scalar_expressions(&mut self, rewrite: &mut dyn FnMut(&mut ScalarExpr)) {
513        match self {
514            Self::Query(query) => rewrite_query_scalars(query, rewrite),
515            Self::Command(command) => rewrite_command_scalars(command, rewrite),
516        }
517    }
518}
519
520impl CommandPlan {
521    #[must_use]
522    pub fn name(&self) -> &'static str {
523        match self {
524            Self::CreateTable(_) => "CreateTable",
525            Self::CreateTableIfNotExists(_) => "CreateTableIfNotExists",
526            Self::CreateIndex(_) => "CreateIndex",
527            Self::Insert(_) => "Insert",
528            Self::Update(_) => "Update",
529            Self::Delete(_) => "Delete",
530            Self::Drop(_) => "Drop",
531            Self::AlterTable(_) => "AlterTable",
532            Self::AlterView(_) => "AlterView",
533            Self::CreateView { .. } => "CreateView",
534            Self::CreateMaterializedView { .. } => "CreateMaterializedView",
535            Self::RefreshMaterializedView { .. } => "RefreshMaterializedView",
536            Self::CreateSchema { .. } => "CreateSchema",
537            Self::AlterSchemaOwner { .. } => "AlterSchemaOwner",
538            Self::Notify { .. } => "Notify",
539            Self::Listen { .. } => "Listen",
540            Self::Unlisten { .. } => "Unlisten",
541            Self::SetVariable { .. } => "SetVariable",
542            Self::ResetVariable { .. } => "ResetVariable",
543            Self::ResetAllVariables => "ResetAllVariables",
544            Self::SetConstraints { .. } => "SetConstraints",
545            Self::ShowVariable { .. } => "ShowVariable",
546            Self::Discard { .. } => "Discard",
547            Self::Load { .. } => "Load",
548            Self::Explain { .. } => "Explain",
549            Self::Analyze { .. } => "Analyze",
550            Self::Vacuum(_) => "Vacuum",
551            Self::Truncate { .. } => "Truncate",
552            Self::Transaction(_) => "Transaction",
553            Self::DeclareCursor { .. } => "DeclareCursor",
554            Self::FetchCursor(_) => "FetchCursor",
555            Self::CloseCursor { .. } => "CloseCursor",
556            Self::CreateSequence(_) => "CreateSequence",
557            Self::CreateDomain(_) => "CreateDomain",
558            Self::AlterSequence(_) => "AlterSequence",
559            Self::CreateTableAs { .. } => "CreateTableAs",
560            Self::Prepare { .. } => "Prepare",
561            Self::Execute { .. } => "Execute",
562            Self::Deallocate { .. } => "Deallocate",
563            Self::CreateForeignServer(_) => "CreateForeignServer",
564            Self::CreateForeignTable(_) => "CreateForeignTable",
565            Self::CreateForeignTableIfNotExists(_) => "CreateForeignTableIfNotExists",
566            Self::AlterForeignTable(_) => "AlterForeignTable",
567            Self::Merge(_) => "Merge",
568            Self::CreateFunction(_) => "CreateFunction",
569            Self::DropFunction(_) => "DropFunction",
570            Self::AlterRoutine(_) => "AlterRoutine",
571            Self::AlterRoutineOwner(_) => "AlterRoutineOwner",
572            Self::RenameRoutine(_) => "RenameRoutine",
573            Self::GrantRoutine(_) => "GrantRoutine",
574            Self::GrantTable(_) => "GrantTable",
575            Self::GrantSequence(_) => "GrantSequence",
576            Self::GrantDatabase(_) => "GrantDatabase",
577            Self::GrantSchema(_) => "GrantSchema",
578            Self::GrantRole(_) => "GrantRole",
579            Self::CreateRole(_) => "CreateRole",
580            Self::AlterRole(_) => "AlterRole",
581            Self::DropRole(_) => "DropRole",
582            Self::CreateTrigger(_) => "CreateTrigger",
583            Self::DropTrigger(_) => "DropTrigger",
584            Self::CreateRule(_) => "CreateRule",
585            Self::DropRule(_) => "DropRule",
586            Self::DoBlock { .. } => "DoBlock",
587            Self::Call { .. } => "Call",
588        }
589    }
590}