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