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