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