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