Skip to main content

uqa_sql/ast/
from.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use serde::{Deserialize, Serialize};
8
9use super::{Expr, FunctionBinding, InternalRelationId, SelectStmt};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum FromClause {
13    /// `FROM <table> [AS <alias>]`.
14    Table {
15        /// Durable catalog identity, including an explicit schema when present.
16        name: String,
17        /// Relation name visible to SQL column binding before an alias is applied.
18        qualifier: String,
19        alias: Option<String>,
20        /// Positional names exposed by the range-table alias.
21        #[serde(default, skip_serializing_if = "Vec::is_empty")]
22        column_aliases: Vec<String>,
23        /// Ordinary references include inheritance children; `ONLY table`
24        /// clears this flag.
25        #[serde(default = "default_include_descendants")]
26        include_descendants: bool,
27    },
28    /// `FROM left <kind> right ON predicate`. `lateral` is true when
29    /// the right side is a LATERAL subquery / function -- the engine
30    /// re-evaluates it for every left row.
31    Join {
32        left: Box<FromClause>,
33        right: Box<FromClause>,
34        kind: JoinKind,
35        /// Boolean qualification supplied by `ON`. This is mutually
36        /// exclusive with `using` and `natural` in parser-produced trees.
37        on: Option<Expr>,
38        /// `PostgreSQL` `USING (column, ...) [AS alias]` metadata. The column
39        /// list must remain explicit until both input row types are known so
40        /// binding can validate each side and construct the merged output.
41        #[serde(default, skip_serializing_if = "Option::is_none")]
42        using: Option<JoinUsing>,
43        /// `NATURAL` derives its `USING` list from the visible columns of both
44        /// input row types at binding time.
45        #[serde(default)]
46        natural: bool,
47        /// Alias applied to the complete parenthesized JOIN result. When
48        /// present, the input relation names are hidden from the enclosing
49        /// query level.
50        #[serde(default, skip_serializing_if = "Option::is_none")]
51        alias: Option<String>,
52        /// Positional aliases for the JOIN output after USING/NATURAL shaping.
53        #[serde(default, skip_serializing_if = "Vec::is_empty")]
54        column_aliases: Vec<String>,
55        #[allow(dead_code)]
56        lateral: bool,
57    },
58    /// `FROM (VALUES (...)...) [AS <alias>(<col_aliases>)]`.
59    Values {
60        rows: Vec<Vec<Expr>>,
61        alias: Option<String>,
62        column_aliases: Vec<String>,
63        /// Opaque identity for an engine-injected, SQL-invisible VALUES row
64        /// carrier. Parser-produced VALUES sources always leave this unset.
65        #[serde(default, skip_serializing_if = "Option::is_none")]
66        #[doc(hidden)]
67        internal_relation: Option<InternalRelationId>,
68        /// Declared physical attribute types for an internal VALUES carrier;
69        /// needed even when the carrier has zero rows.
70        #[serde(default, skip_serializing_if = "Vec::is_empty")]
71        #[doc(hidden)]
72        internal_column_types: Vec<Option<super::ColumnType>>,
73    },
74    /// `FROM <fn>(<args>) [AS <alias>(<col_aliases>)]` -- e.g.
75    /// `generate_series(1, 5)`, `unnest(arr)`, `regexp_split_to_table`,
76    /// `json_each(...)`, `cypher(...) AS (col agtype, ...)`. The engine
77    /// dispatches by name.
78    Function {
79        name: String,
80        /// Exact catalog routine identity for a stored expression tree. Parser-produced trees leave this unset and catalog owners bind it before persistence.
81        #[serde(default, skip_serializing_if = "Option::is_none")]
82        binding: Option<FunctionBinding>,
83        /// Local function identifier used as `PostgreSQL`'s default output column label. Kept separate from the catalog-qualified lookup name so quoted identifiers containing `.` remain indivisible.
84        output_name: String,
85        /// Catalog relations bound to a tuple-producing operator join, kept separate from scalar arguments so each operand retains its own name-resolution, dependency, planning, and execution context.
86        #[serde(default, skip_serializing_if = "Option::is_none")]
87        relations: Option<OperatorJoinRelations>,
88        args: Vec<Expr>,
89        alias: Option<String>,
90        column_aliases: Vec<String>,
91        /// Append `PostgreSQL`'s one-based `bigint` ordinality column after the function's ordinary output columns.
92        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
93        ordinality: bool,
94        /// Declared column types when the alias used a column
95        /// definition list (`AS (col agtype, n int)`); empty when the
96        /// alias only renamed columns. Type names are lowercased
97        /// `PostgreSQL` internal names (`agtype`, `int4`, `text`, ...).
98        #[serde(default)]
99        column_types: Vec<String>,
100    },
101    /// One `PostgreSQL` range-function group. This represents explicit
102    /// `ROWS FROM (...)` syntax and the parser transform of an unqualified
103    /// multi-argument `unnest(a, b, ...)` into independent unary
104    /// `pg_catalog.unnest` members. Members are evaluated independently and
105    /// their result columns are concatenated in declaration order.
106    FunctionGroup {
107        functions: Vec<TableFunction>,
108        /// Alias applied to the complete group rather than to an individual
109        /// member.
110        alias: Option<String>,
111        /// Positional aliases for the concatenated group output.
112        column_aliases: Vec<String>,
113        /// Append one group-wide, one-based `bigint` ordinality column after
114        /// every member's ordinary output columns.
115        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
116        ordinality: bool,
117    },
118    /// `FROM (SELECT ...) AS <alias>` -- subquery as a relation.
119    /// The body re-runs as if a CTE; the alias renames the result
120    /// columns when supplied.
121    Subquery {
122        body: Box<SelectStmt>,
123        alias: Option<String>,
124        column_aliases: Vec<String>,
125    },
126}
127
128const fn default_include_descendants() -> bool {
129    true
130}
131
132/// One function inside a [`FromClause::FunctionGroup`].
133///
134/// A member owns its column definition list because `ROWS FROM` permits a
135/// distinct `AS (name type, ...)` clause after each call. The range item's
136/// relation alias, positional aliases, and ordinality remain on the enclosing
137/// group.
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct TableFunction {
140    pub name: String,
141    /// Exact catalog routine identity for a stored expression tree. Parser-produced trees leave this unset and catalog owners bind it before persistence.
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub binding: Option<FunctionBinding>,
144    pub output_name: String,
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub relations: Option<OperatorJoinRelations>,
147    pub args: Vec<Expr>,
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub column_aliases: Vec<String>,
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub column_types: Vec<String>,
152}
153
154/// The two independently bound catalog relations consumed by an operator-join table function.
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct OperatorJoinRelations {
157    pub left: String,
158    pub right: String,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub struct JoinUsing {
163    pub columns: Vec<String>,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub alias: Option<String>,
166}
167
168impl FromClause {
169    /// All table names referenced under this clause, in declaration
170    /// order. Used by the compiler to resolve unqualified column refs.
171    pub fn collect_tables(&self, out: &mut Vec<(String, Option<String>)>) {
172        match self {
173            FromClause::Table {
174                name,
175                qualifier,
176                alias,
177                ..
178            } => out.push((
179                name.clone(),
180                Some(alias.as_ref().unwrap_or(qualifier).clone()),
181            )),
182            FromClause::Join { left, right, .. } => {
183                left.collect_tables(out);
184                right.collect_tables(out);
185            }
186            FromClause::Values { alias, .. }
187            | FromClause::Function { alias, .. }
188            | FromClause::FunctionGroup { alias, .. }
189            | FromClause::Subquery { alias, .. } => {
190                if let Some(a) = alias {
191                    out.push((a.clone(), Some(a.clone())));
192                }
193            }
194        }
195    }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub enum JoinKind {
200    Inner,
201    Left,
202    Right,
203    Full,
204    Cross,
205}