Skip to main content

uqa_sql/plan/
model.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Serializable relational, command, source, and scalar plan data model.
8
9use super::{NullsOrder, ScalarExpr, SetOpKind};
10use crate::catalog::roles::RoleReference;
11
12const fn default_include_descendants() -> bool {
13    true
14}
15
16/// One fully lowered SQL statement.
17///
18/// There is deliberately no `Legacy`, `Opaque`, or raw-`Statement` variant:
19/// adding a SQL statement kind must update the exhaustive lowerer and the
20/// physical driver.
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22pub enum UnifiedPlan {
23    Query(Box<QueryPlan>),
24    Command(Box<CommandPlan>),
25}
26
27/// A relational query with its CTE scope and one relational root.
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
29pub struct QueryPlan {
30    /// Whether relation references in this tree are stored catalog identities rather than names that must be resolved in the executing session.
31    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
32    pub relations_bound: bool,
33    pub ctes: Vec<CtePlan>,
34    pub root: RelationalPlan,
35}
36
37/// A named query child owned by a [`QueryPlan`].
38#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
39pub struct CtePlan {
40    pub name: String,
41    pub columns: Vec<String>,
42    pub recursive: bool,
43    #[serde(default)]
44    pub materialization: crate::ast::CteMaterialization,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub search: Option<CteSearchPlan>,
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub cycle: Option<CteCyclePlan>,
49    #[serde(flatten)]
50    pub body: CtePlanBody,
51}
52
53/// A relational CTE or a command whose RETURNING relation feeds its consumers.
54#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
55pub enum CtePlanBody {
56    #[serde(rename = "query")]
57    Query(Box<QueryPlan>),
58    #[serde(rename = "command")]
59    Command(Box<CommandPlan>),
60}
61
62impl CtePlanBody {
63    pub fn query(&self) -> Option<&QueryPlan> {
64        match self {
65            Self::Query(query) => Some(query),
66            Self::Command(_) => None,
67        }
68    }
69
70    pub fn query_mut(&mut self) -> Option<&mut QueryPlan> {
71        match self {
72            Self::Query(query) => Some(query),
73            Self::Command(_) => None,
74        }
75    }
76
77    pub const fn modifies_data(&self) -> bool {
78        matches!(self, Self::Command(_))
79    }
80
81    /// Whether this WITH definition exposes a relation to its consumers.
82    pub fn returns_rows(&self) -> bool {
83        match self {
84            Self::Query(_) => true,
85            Self::Command(command) => command
86                .returning()
87                .is_some_and(|returning| !returning.is_empty()),
88        }
89    }
90
91    pub fn into_plan(self) -> UnifiedPlan {
92        match self {
93            Self::Query(query) => UnifiedPlan::Query(query),
94            Self::Command(command) => UnifiedPlan::Command(command),
95        }
96    }
97}
98
99impl From<UnifiedPlan> for CtePlanBody {
100    fn from(plan: UnifiedPlan) -> Self {
101        match plan {
102            UnifiedPlan::Query(query) => Self::Query(query),
103            UnifiedPlan::Command(command) => Self::Command(command),
104        }
105    }
106}
107
108/// Generated traversal-order column for a recursive CTE.
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct CteSearchPlan {
111    pub columns: Vec<String>,
112    pub breadth_first: bool,
113    pub sequence_column: String,
114}
115
116/// Generated cycle mark and path columns for a recursive CTE.
117#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
118pub struct CteCyclePlan {
119    pub columns: Vec<String>,
120    pub mark_column: String,
121    pub mark_value: ScalarExpr,
122    pub mark_default: ScalarExpr,
123    pub path_column: String,
124}
125
126/// Relational nodes common to ordinary SQL, retrieval SQL, and table/graph
127/// functions.
128#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
129pub enum RelationalPlan {
130    /// A single SELECT query block. Its source is a separate plan tree and its
131    /// compute phase is classified as projection, aggregation, or windowing.
132    QueryBlock(Box<QueryBlockPlan>),
133    /// SQL set operations own both input plans; combined ordering and slicing
134    /// are properties of the set node rather than either branch.
135    SetOp {
136        kind: SetOpKind,
137        all: bool,
138        left: Box<QueryPlan>,
139        right: Box<QueryPlan>,
140        order_by: Vec<OrderPlan>,
141        limit: Option<Box<ScalarExpr>>,
142        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
143        with_ties: bool,
144        offset: Option<Box<ScalarExpr>>,
145        subqueries: Vec<QueryPlan>,
146    },
147    /// Standalone `VALUES`, used both as a statement and as a relational
148    /// source. Each cell remains an expression so parameters/functions bind at
149    /// execution time.
150    Values {
151        rows: Vec<Vec<ScalarExpr>>,
152        subqueries: Vec<QueryPlan>,
153    },
154}
155
156/// One SELECT block after `WITH` and set-operation structure has been pulled
157/// into explicit parent/child nodes.
158#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
159pub struct QueryBlockPlan {
160    /// The canonical owners of window inputs; calls keep resolved execution copies.
161    #[serde(default, skip_serializing_if = "Vec::is_empty")]
162    pub windows: Vec<crate::ast::WindowDefinition<crate::ir::ScalarWindowSpec>>,
163    pub projections: Vec<ProjectionPlan>,
164    /// Analyzed column reads retained when unused outputs are removed. These names authorize access but never evaluate the discarded values.
165    #[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
166    pub privilege_columns: std::collections::BTreeSet<crate::ColumnIdentity>,
167    pub from: Option<SourcePlan>,
168    pub r#where: Option<ScalarExpr>,
169    pub compute: ComputePlan,
170    pub group_by: Vec<ScalarExpr>,
171    pub grouping_sets: Vec<Vec<ScalarExpr>>,
172    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
173    pub group_distinct: bool,
174    pub having: Option<ScalarExpr>,
175    pub order_by: Vec<OrderPlan>,
176    pub limit: Option<ScalarExpr>,
177    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
178    pub with_ties: bool,
179    pub offset: Option<ScalarExpr>,
180    pub distinct: bool,
181    pub distinct_on: Vec<ScalarExpr>,
182    pub subqueries: Vec<QueryPlan>,
183    pub access: AccessPathPlan,
184    /// `FOR UPDATE` / `FOR SHARE` clauses belonging to this query block.
185    #[serde(default, skip_serializing_if = "Vec::is_empty")]
186    pub locking: Vec<crate::ast::LockingClause>,
187}
188
189/// Cross-paradigm access decision made after the relational and scalar
190/// portions of a query block have both been lowered.
191#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
192pub enum AccessPathPlan {
193    /// Ordinary row-source execution.
194    Row,
195    /// Use the shared document-support/operator algebra for the block predicate.
196    OperatorTree {
197        /// The relational ORDER BY/OFFSET/LIMIT can be pushed into the
198        /// retrieval function before row materialization.
199        score_limit_pushdown: bool,
200    },
201    /// Split a mixed predicate into posting-list candidates followed by
202    /// row-level residual evaluation.
203    Hybrid,
204}
205
206/// Physical strategy selected for a relational join.
207///
208/// `Auto` is used for an unreordered SQL join and lets physical lowering pick
209/// hash execution for a splittable equality predicate or nested-loop execution
210/// otherwise. `Hash` is an optimizer commitment produced by `DPccp` and must be
211/// executable; physical lowering reports an internal planning error if that
212/// invariant is violated.
213#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
214pub enum JoinExecutionStrategy {
215    #[default]
216    Auto,
217    Hash,
218}
219
220/// One independently resolved and bound function inside a range-function group.
221#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
222pub struct TableFunctionPlan {
223    pub name: String,
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub binding: Option<crate::ast::FunctionBinding>,
226    #[serde(default)]
227    pub output_name: String,
228    #[serde(default, skip_serializing_if = "Option::is_none")]
229    pub relations: Option<crate::ast::OperatorJoinRelations>,
230    pub args: Vec<ScalarExpr>,
231    pub column_aliases: Vec<String>,
232    pub column_types: Vec<String>,
233}
234
235/// The row-producing source below a query block.
236#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
237pub enum SourcePlan {
238    Table {
239        name: String,
240        #[serde(default)]
241        qualifier: String,
242        alias: Option<String>,
243        #[serde(default, skip_serializing_if = "Vec::is_empty")]
244        column_aliases: Vec<String>,
245        #[serde(default, skip_serializing_if = "Option::is_none")]
246        bound_columns: Option<Vec<String>>,
247        #[serde(default = "default_include_descendants")]
248        include_descendants: bool,
249    },
250    Join {
251        left: Box<SourcePlan>,
252        right: Box<SourcePlan>,
253        kind: crate::ast::JoinKind,
254        on: Option<ScalarExpr>,
255        #[serde(default, skip_serializing_if = "Option::is_none")]
256        using: Option<crate::ast::JoinUsing>,
257        #[serde(default)]
258        natural: bool,
259        #[serde(default, skip_serializing_if = "Option::is_none")]
260        alias: Option<String>,
261        #[serde(default, skip_serializing_if = "Vec::is_empty")]
262        column_aliases: Vec<String>,
263        lateral: bool,
264        #[serde(default)]
265        strategy: JoinExecutionStrategy,
266    },
267    Values {
268        rows: Vec<Vec<ScalarExpr>>,
269        alias: Option<String>,
270        column_aliases: Vec<String>,
271        #[serde(default, skip_serializing_if = "Option::is_none")]
272        internal_relation: Option<crate::ast::InternalRelationId>,
273        #[serde(default, skip_serializing_if = "Vec::is_empty")]
274        internal_column_types: Vec<Option<crate::ast::ColumnType>>,
275    },
276    Function {
277        name: String,
278        #[serde(default, skip_serializing_if = "Option::is_none")]
279        binding: Option<crate::ast::FunctionBinding>,
280        #[serde(default)]
281        output_name: String,
282        #[serde(default, skip_serializing_if = "Option::is_none")]
283        relations: Option<crate::ast::OperatorJoinRelations>,
284        args: Vec<ScalarExpr>,
285        alias: Option<String>,
286        column_aliases: Vec<String>,
287        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
288        ordinality: bool,
289        column_types: Vec<String>,
290    },
291    FunctionGroup {
292        functions: Vec<TableFunctionPlan>,
293        alias: Option<String>,
294        column_aliases: Vec<String>,
295        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
296        ordinality: bool,
297    },
298    Subquery {
299        body: Box<QueryPlan>,
300        alias: Option<String>,
301        column_aliases: Vec<String>,
302    },
303}
304
305/// The SELECT-list phase chosen during lowering.
306#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
307pub enum ComputePlan {
308    Project,
309    Aggregate,
310    Window,
311}
312
313#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
314pub struct ProjectionPlan {
315    pub expr: ScalarExpr,
316    pub alias: Option<String>,
317}
318
319#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
320pub struct OrderPlan {
321    pub expr: ScalarExpr,
322    pub descending: bool,
323    pub nulls: Option<NullsOrder>,
324}
325
326/// Executable scalar IR plus every query-valued descendant it owns.
327#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
328pub struct ExpressionPlan {
329    pub scalar: ScalarExpr,
330    pub subqueries: Vec<QueryPlan>,
331}
332
333#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
334pub struct AssignmentPlan {
335    #[serde(rename = "column")]
336    pub target: crate::ast::AssignmentTargets<ScalarExpr>,
337    pub value: ScalarExpr,
338}
339
340impl AssignmentPlan {
341    pub fn expressions(&self) -> impl Iterator<Item = &ScalarExpr> {
342        self.target
343            .expressions()
344            .chain(std::iter::once(&self.value))
345    }
346
347    pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut ScalarExpr> {
348        self.target
349            .expressions_mut()
350            .chain(std::iter::once(&mut self.value))
351    }
352}
353
354#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
355pub struct ViewCheckPlan {
356    pub view: String,
357    pub predicate: ScalarExpr,
358}
359
360#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
361pub struct ViewRuleReturningPlan {
362    pub relation: String,
363    pub target_qualifier: String,
364    pub returning: Vec<ProjectionPlan>,
365    pub aliases: crate::ast::ReturningAliases,
366    pub subqueries: Vec<QueryPlan>,
367}
368
369#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
370pub struct ViewRuleInsertPlan {
371    pub relation: String,
372    pub supplied_columns: Vec<String>,
373    pub input_columns: Vec<String>,
374}
375
376#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
377pub struct ViewRuleUpdatePlan {
378    pub relation: String,
379    pub assigned_columns: Vec<String>,
380    pub input_columns: Vec<String>,
381}
382
383#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
384pub struct InsertPlan {
385    pub table: String,
386    pub target_relation_bound: bool,
387    /// Whether non-target relation references are stored catalog identities rather than names that must be resolved in the executing session.
388    pub relations_bound: bool,
389    /// Effective role used for non-target privilege checks in an internally rewritten statement.
390    pub statement_privilege_subject: Option<RoleReference>,
391    /// Effective role used only for privilege checks on an internally rewritten target relation.
392    pub target_privilege_subject: Option<RoleReference>,
393    pub target_qualifier: String,
394    #[serde(default)]
395    pub target_alias: Option<String>,
396    pub include_descendants: bool,
397    pub columns: Vec<crate::ast::AssignmentTarget<ScalarExpr>>,
398    #[serde(default, skip_serializing_if = "Option::is_none")]
399    pub overriding: Option<crate::ast::OverridingKind>,
400    pub ctes: Vec<CtePlan>,
401    pub rows: Vec<Vec<ScalarExpr>>,
402    pub source: Option<Box<QueryPlan>>,
403    pub on_conflict: Option<ConflictPlan>,
404    pub returning: Vec<ProjectionPlan>,
405    pub returning_aliases: crate::ast::ReturningAliases,
406    pub subqueries: Vec<QueryPlan>,
407    pub view_checks: Vec<ViewCheckPlan>,
408    pub view_rule_relations: Vec<String>,
409    pub view_rule_insert_plans: Vec<ViewRuleInsertPlan>,
410    pub view_rule_returning: Option<ViewRuleReturningPlan>,
411}
412
413#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
414pub struct ConflictPlan {
415    pub predicate: Option<Box<ScalarExpr>>,
416    pub constraint: Option<String>,
417    pub conflict_columns: Vec<String>,
418    pub expressions: Vec<ScalarExpr>,
419    pub action: ConflictActionPlan,
420}
421
422#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
423pub enum ConflictActionPlan {
424    Nothing,
425    Update {
426        assignments: Vec<AssignmentPlan>,
427        predicate: Option<Box<ScalarExpr>>,
428    },
429}
430
431#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
432pub struct UpdatePlan {
433    pub table: String,
434    pub target_relation_bound: bool,
435    /// Whether non-target relation references are stored catalog identities rather than names that must be resolved in the executing session.
436    pub relations_bound: bool,
437    /// Effective role used for non-target privilege checks in an internally rewritten statement.
438    pub statement_privilege_subject: Option<RoleReference>,
439    /// Effective role used only for privilege checks on an internally rewritten target relation.
440    pub target_privilege_subject: Option<RoleReference>,
441    pub target_qualifier: String,
442    #[serde(default)]
443    pub target_alias: Option<String>,
444    pub include_descendants: bool,
445    pub assignments: Vec<AssignmentPlan>,
446    pub predicate: Option<ScalarExpr>,
447    pub ctes: Vec<CtePlan>,
448    pub source: Option<Box<SourcePlan>>,
449    pub returning: Vec<ProjectionPlan>,
450    pub returning_aliases: crate::ast::ReturningAliases,
451    pub subqueries: Vec<QueryPlan>,
452    pub view_checks: Vec<ViewCheckPlan>,
453    pub view_rule_relations: Vec<String>,
454    pub view_rule_update_plans: Vec<ViewRuleUpdatePlan>,
455    pub view_rule_returning: Option<ViewRuleReturningPlan>,
456}
457
458#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
459pub struct DeletePlan {
460    pub table: String,
461    pub target_relation_bound: bool,
462    /// Whether non-target relation references are stored catalog identities rather than names that must be resolved in the executing session.
463    pub relations_bound: bool,
464    /// Effective role used for non-target privilege checks in an internally rewritten statement.
465    pub statement_privilege_subject: Option<RoleReference>,
466    /// Effective role used only for privilege checks on an internally rewritten target relation.
467    pub target_privilege_subject: Option<RoleReference>,
468    pub target_qualifier: String,
469    #[serde(default)]
470    pub target_alias: Option<String>,
471    pub include_descendants: bool,
472    pub predicate: Option<ScalarExpr>,
473    pub ctes: Vec<CtePlan>,
474    pub source: Option<Box<SourcePlan>>,
475    pub returning: Vec<ProjectionPlan>,
476    pub returning_aliases: crate::ast::ReturningAliases,
477    pub subqueries: Vec<QueryPlan>,
478    pub view_rule_relations: Vec<String>,
479    pub view_rule_returning: Option<ViewRuleReturningPlan>,
480}
481
482#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
483pub struct MergePlan {
484    #[serde(default)]
485    pub ctes: Vec<CtePlan>,
486    pub target: String,
487    /// Effective role used for non-target privilege checks in an internally rewritten statement.
488    pub statement_privilege_subject: Option<RoleReference>,
489    /// Effective role used only for privilege checks on an internally rewritten target relation.
490    pub target_privilege_subject: Option<RoleReference>,
491    pub target_qualifier: String,
492    pub target_alias: Option<String>,
493    pub include_descendants: bool,
494    pub target_predicate: Option<ScalarExpr>,
495    pub source: Box<SourcePlan>,
496    pub join_condition: ScalarExpr,
497    pub when_clauses: Vec<MergeWhenPlan>,
498    pub returning: Vec<ProjectionPlan>,
499    pub returning_aliases: crate::ast::ReturningAliases,
500    pub subqueries: Vec<QueryPlan>,
501    pub view_checks: Vec<ViewCheckPlan>,
502}
503
504#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
505pub enum MergeWhenPlan {
506    UpdateMatched {
507        condition: Option<ScalarExpr>,
508        assignments: Vec<AssignmentPlan>,
509    },
510    DeleteMatched {
511        condition: Option<ScalarExpr>,
512    },
513    UpdateNotMatchedBySource {
514        condition: Option<ScalarExpr>,
515        assignments: Vec<AssignmentPlan>,
516    },
517    DeleteNotMatchedBySource {
518        condition: Option<ScalarExpr>,
519    },
520    InsertNotMatched {
521        condition: Option<ScalarExpr>,
522        columns: Vec<crate::ast::AssignmentTarget<ScalarExpr>>,
523        #[serde(default, skip_serializing_if = "Option::is_none")]
524        overriding: Option<crate::ast::OverridingKind>,
525        values: Vec<ScalarExpr>,
526    },
527    NothingMatched {
528        condition: Option<ScalarExpr>,
529    },
530    NothingNotMatched {
531        condition: Option<ScalarExpr>,
532    },
533    NothingNotMatchedBySource {
534        condition: Option<ScalarExpr>,
535    },
536}
537
538/// Non-query statement plans. Mutations own physical sources and scalar IR;
539/// query-bearing catalog commands own explicit query children. Typed DDL and
540/// procedural payloads contain catalog data, never a second SQL dispatcher.
541#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
542pub enum CommandPlan {
543    CreateTable(Box<crate::ast::CreateTable>),
544    CreateTableIfNotExists(crate::ast::DeferredCreateTable),
545    CreateIndex(crate::ast::CreateIndex),
546    RenameIndex(crate::ast::RenameIndexStmt),
547    Insert(Box<InsertPlan>),
548    Update(Box<UpdatePlan>),
549    Delete(Box<DeletePlan>),
550    Drop(crate::ast::DropStmt),
551    AlterTable(Box<crate::ast::AlterTableStmt>),
552    AlterView(crate::ast::AlterViewStmt),
553    CreateView {
554        name: String,
555        column_names: Vec<String>,
556        query: Box<QueryPlan>,
557        or_replace: bool,
558        persistence: crate::ast::RelationPersistence,
559        options: Vec<(String, String)>,
560    },
561    CreateMaterializedView {
562        name: String,
563        column_names: Vec<String>,
564        if_not_exists: bool,
565        with_no_data: bool,
566        options: Vec<(String, String)>,
567        query: Box<QueryPlan>,
568    },
569    RefreshMaterializedView {
570        name: String,
571        concurrently: bool,
572        with_no_data: bool,
573    },
574    CreateSchema {
575        name: Option<String>,
576        if_not_exists: bool,
577        #[serde(default, skip_serializing_if = "Option::is_none")]
578        authorization: Option<crate::ast::SchemaAuthorization>,
579    },
580    AlterSchemaOwner {
581        name: String,
582        new_owner: crate::ast::RoleSpecification,
583    },
584    RenameSchema {
585        name: String,
586        new_name: String,
587    },
588    Notify {
589        channel: String,
590        payload: String,
591    },
592    Listen {
593        channel: String,
594    },
595    Unlisten {
596        channel: Option<String>,
597    },
598    SetVariable {
599        name: String,
600        value: String,
601        #[serde(default)]
602        local: bool,
603        #[serde(default)]
604        is_default: bool,
605    },
606    ResetVariable {
607        name: String,
608    },
609    ResetAllVariables,
610    SetConstraints {
611        constraints: Vec<crate::ast::SetConstraintName>,
612        deferred: bool,
613    },
614    ShowVariable {
615        name: String,
616    },
617    Discard {
618        target: crate::ast::DiscardTarget,
619    },
620    Load {
621        library: String,
622    },
623    Explain {
624        analyze: bool,
625        verbose: bool,
626        format: Option<String>,
627        body: Box<UnifiedPlan>,
628    },
629    Analyze {
630        table: Option<String>,
631    },
632    Vacuum(crate::ast::VacuumStmt),
633    LockTable(crate::ast::LockTableStmt),
634    Truncate {
635        tables: Vec<crate::ast::TruncateTarget>,
636        cascade: bool,
637        restart_identity: bool,
638    },
639    Transaction(crate::ast::TransactionStmt),
640    DeclareCursor {
641        name: String,
642        binary: bool,
643        scroll: Option<bool>,
644        hold: bool,
645        query: Box<QueryPlan>,
646    },
647    FetchCursor(crate::ast::FetchCursorStmt),
648    CloseCursor {
649        name: Option<String>,
650    },
651    CreateSequence(crate::ast::CreateSequence),
652    CreateDomain(crate::ast::CreateDomain),
653    AlterDomain(crate::ast::AlterDomain),
654    CreateEnum(crate::ast::CreateEnum),
655    CreateCompositeType(crate::ast::CreateCompositeType),
656    AlterEnum(crate::ast::AlterEnum),
657    AlterTypeObject(crate::ast::AlterTypeObject),
658    GrantType(crate::ast::GrantTypeStmt),
659    AlterSequence(crate::ast::AlterSequence),
660    CreateTableAs {
661        name: String,
662        if_not_exists: bool,
663        column_names: Vec<String>,
664        with_no_data: bool,
665        /// Written as `SELECT ... INTO`, which `PostgreSQL` tags `SELECT INTO`.
666        #[serde(default)]
667        select_into: bool,
668        persistence: crate::ast::RelationPersistence,
669        on_commit: crate::ast::OnCommitAction,
670        query: Box<QueryPlan>,
671    },
672    Prepare {
673        name: String,
674        #[serde(default)]
675        parameter_types: Vec<crate::ast::ColumnType>,
676        body: Box<UnifiedPlan>,
677    },
678    Execute {
679        name: String,
680        params: Vec<ExpressionPlan>,
681    },
682    Deallocate {
683        name: Option<String>,
684    },
685    CreateForeignWrapper(crate::ast::CreateForeignWrapper),
686    CreateForeignServer(crate::ast::CreateForeignServer),
687    CreateForeignTable(crate::ast::CreateForeignTable),
688    #[serde(alias = "CreateForeignTableIfNotExists")]
689    CreateForeignTableDefinition(crate::ast::DeferredCreateForeignTable),
690    AlterForeignTable(crate::ast::AlterForeignTableStmt),
691    Merge(Box<MergePlan>),
692    CreateFunction(Box<crate::ast::CreateFunction>),
693    DropFunction(crate::ast::DropFunctionStmt),
694    AlterRoutine(crate::ast::AlterRoutineStmt),
695    AlterRoutineOwner(crate::ast::AlterRoutineOwnerStmt),
696    RenameRoutine(crate::ast::RenameRoutineStmt),
697    GrantRoutine(crate::ast::GrantRoutineStmt),
698    GrantTable(crate::ast::GrantTableStmt),
699    GrantSequence(crate::ast::GrantSequenceStmt),
700    GrantDatabase(crate::ast::GrantDatabaseStmt),
701    GrantSchema(crate::ast::GrantSchemaStmt),
702    GrantRole(crate::ast::GrantRoleStmt),
703    CreateRole(crate::ast::CreateRoleStmt),
704    AlterRole(crate::ast::AlterRoleStmt),
705    RenameRole(crate::ast::RenameRoleStmt),
706    DropRole(crate::ast::DropRoleStmt),
707    CreateTrigger(crate::ast::CreateTrigger),
708    DropTrigger(crate::ast::DropTrigger),
709    CreateRule(crate::ast::CreateRule),
710    DropRule(crate::ast::DropRule),
711    DoBlock {
712        language: String,
713        body: String,
714    },
715    Call {
716        name: String,
717        args: Vec<ExpressionPlan>,
718    },
719}
720
721/// Classification hook for engine-registered aggregate functions. Built-in
722/// aggregates are always recognised; the callback extends that set without
723/// making the planner depend on the engine.
724pub trait AggregateClassifier {
725    fn is_registered_aggregate(&self, name: &str) -> bool;
726}
727
728impl<F> AggregateClassifier for F
729where
730    F: Fn(&str) -> bool,
731{
732    fn is_registered_aggregate(&self, name: &str) -> bool {
733        self(name)
734    }
735}
736
737pub(super) struct NoRegisteredAggregates;
738
739impl AggregateClassifier for NoRegisteredAggregates {
740    fn is_registered_aggregate(&self, _name: &str) -> bool {
741        false
742    }
743}