pub struct JoinSyntax {
pub stacked_join_qualifiers: bool,
pub full_outer_join: bool,
pub natural_cross_join: bool,
pub straight_join: bool,
pub asof_join: bool,
pub positional_join: bool,
pub semi_anti_join: bool,
pub sided_semi_anti_join: bool,
pub apply_join: bool,
pub recursive_search_cycle: bool,
pub recursive_union_rejects_order_limit: bool,
pub recursive_using_key: bool,
}Expand description
Dialect-owned join-operator and recursive-query relation-composition syntax accepted by the parser.
The join-family flags — the join operators and their side/qualifier variants beyond
the always-available inner/left/right/cross joins — together with the recursive-query
structural clauses that compose relations. Split out of TableExpressionSyntax at
its 16-field line as the relation-composition axis, distinct from the table-factor and
factor-tail/alias cores. Each flag is a grammar gate: when off the keyword is left to
the identifier grammar or surfaces as a clean parse error.
Fields§
§stacked_join_qualifiers: boolAccept stacked join qualifiers — the PostgreSQL right-nesting where two joins
precede their ON/USING constraints and the nearest constraint closes the
innermost join: a JOIN b JOIN c ON p ON q reads as a JOIN (b JOIN c ON p) ON q
(PostgreSQL table_ref: … | joined_table right-recursion). On for ANSI/PostgreSQL/
MySQL/DuckDB/Lenient. SQLite’s join-clause is flat — each join-operator takes
exactly one immediately-following constraint — so it is off; the right operand is
not extended past the first constraint, and a second stacked ON/USING
(… USING (id) USING (id)) is then left unconsumed and surfaces as the syntax
error SQLite reports (engine-measured via rusqlite). The common a JOIN b ON x JOIN c ON y — each constraint right after its own join — needs no nesting and is
unaffected either way.
full_outer_join: boolAccept the FULL [OUTER] JOIN bilateral outer join. On for ANSI/PostgreSQL/SQLite/
DuckDB/Lenient. MySQL has no FULL join (it offers only LEFT/RIGHT outer joins),
so it is off there: the FULL join-side keyword is not consumed, and an
already-aliased factor followed by FULL [OUTER] JOIN (a x FULL OUTER JOIN b) is
then a clean parse error — exactly what MySQL reports (engine-measured-rejected on
mysql:8). Because FULL is a non-reserved word in MySQL, a bare a full JOIN b
still reads full as the factor’s alias (grabbed before the join loop), matching the
engine — this flag only governs the join-side reading, never the alias.
natural_cross_join: boolAccept NATURAL CROSS JOIN — NATURAL prefixing the CROSS join keyword.
SQLite’s join-operator validates its up-to-three keywords post-hoc and admits
NATURAL before any join type, CROSS included; PostgreSQL and DuckDB both
parse-reject it (engine-probed). Because CROSS JOIN is the optimizer-hint
spelling of INNER JOIN and NATURAL supplies the shared-column constraint,
NATURAL CROSS JOIN is semantically a natural inner join — engine-probed on
rusqlite: it yields the shared-column equijoin’s row/column shape, not the cross
product — so the parser normalizes it into the canonical
JoinOperator::Inner + JoinConstraint::Natural
shape (the NATURAL INNER precedent, where the redundant side keyword is likewise
dropped), rendering back as NATURAL JOIN. No new AST field is needed: the
round-trip oracle compares structure, not spelling, so the elided CROSS word
re-parses to the same AST. On for SQLite / Lenient, off elsewhere; when off,
NATURAL CROSS fails the NATURAL arm’s mandatory JOIN and rejects unchanged.
straight_join: boolAccept MySQL’s STRAIGHT_JOIN join-order hint, in both of its surfaces: the
join operator a STRAIGHT_JOIN b [ON ...] and the SELECT STRAIGHT_JOIN ...
modifier. One flag gates both grammar points because they are a single dialect
unit — MySQL always admits the modifier wherever it admits the join keyword
(mirroring how CreateTableClauseSyntax::table_options gates the trailing options
and the AUTO_INCREMENT attribute together). Both fold into the
canonical inner-join shape / Select flag with a surface tag, never a new node.
When off, STRAIGHT_JOIN is left to the identifier grammar (a non-reserved word
under ANSI/PostgreSQL), so the MySQL construct is a clean parse divergence.
asof_join: boolAccept DuckDB’s ASOF [INNER|LEFT|RIGHT|FULL [OUTER]] JOIN inexact-match
temporal join (JoinOperator::AsOf). When off,
ASOF is left to the identifier grammar (a non-reserved word under
ANSI/PostgreSQL) — and because the next word is JOIN, the text still parses
there, as an aliased plain join (a different-tree reading, unlike
STRAIGHT_JOIN’s leftover-input reject). The flag alone is not enough for the
DuckDB meaning on a bare table factor — the word must also be in
FeatureSet::reserved_column_name or the factor’s alias swallows it first
(the DuckDb preset reserves it; LENIENT keeps the ANSI reserved model, so
there the join parses only after an explicit alias). On for DuckDb / Lenient,
off elsewhere.
positional_join: boolAccept DuckDB’s POSITIONAL JOIN row-position pairing join
(JoinOperator::Positional), which takes no
ON/USING constraint and no side keyword. The same reserved-word
interaction as asof_join applies. On for DuckDb /
Lenient, off elsewhere.
semi_anti_join: boolAccept DuckDB’s SEMI JOIN / ANTI JOIN semi-/anti-join operators
(JoinOperator::Semi/Anti),
including their NATURAL and ASOF compositions (NATURAL SEMI JOIN,
ASOF ANTI JOIN). One flag gates both because SEMI and ANTI are the same
grammar production (a join_type keyword taking an ON/USING constraint) that
DuckDB only ever ships together — the paired-flag doctrine (the
straight_join precedent), unlike the distinct-grammar
asof_join/positional_join pair.
The same reserved-word interaction as asof_join applies: the
DuckDb preset reserves both words as a ColId/bare-alias so a bare
l SEMI JOIN r reads the join rather than aliasing l; LENIENT keeps the ANSI
reserved model, so there the join parses only after an explicit alias. On for
DuckDb / Lenient, off elsewhere.
sided_semi_anti_join: boolAccept the Spark/Hive/Databricks sided semi-/anti-join spelling
({LEFT|RIGHT} {SEMI|ANTI} JOIN), recorded as the
SemiAntiSide axis on the same
JoinOperator::Semi/Anti
operators as DuckDB’s side-less form. A separate gate from
semi_anti_join because it is a different engine family:
DuckDB parse-rejects LEFT SEMI JOIN (engine-probed), so folding the sided
spelling into semi_anti_join would over-accept it under the DuckDb preset. The
leading LEFT/RIGHT keyword is already a reserved join side, so — unlike the
keyword-led asof_join pair — no reserved-word interplay is
needed: the preceding factor’s alias can never swallow it, and a plain
LEFT [OUTER] JOIN is disambiguated by the following SEMI/ANTI keyword. One
flag gates the whole sided family (both sides × SEMI/ANTI) as one grammar
production (the straight_join/semi_anti_join
paired-flag doctrine). The sided form always takes an ON/USING constraint and
never composes with NATURAL/ASOF. On for the Databricks and Hive presets (whose
engine family documents the spelling — Hive originated LEFT SEMI JOIN) and for
Lenient (the permissive superset); off elsewhere, where the other engines
parse-reject the sided spelling. The atomic flag admits the RIGHT-sided and ANTI
spellings those presets do not all document — a known conservative-direction
over-acceptance a future side-refinement would tighten.
apply_join: boolAccept MSSQL’s CROSS APPLY / OUTER APPLY join operators
(JoinOperator::Apply) — a lateral-correlated join
over a right table factor (derived table or table-valued function), taking no
ON/USING constraint. One flag gates the whole APPLY family (both the
CROSS and OUTER flavours) because they are a single grammar production
differing only by that keyword (the straight_join
paired-flag doctrine; the ApplyKind axis carries the
flavour). No reserved-word interplay is needed — the leading CROSS/OUTER
keyword already anchors the operator, so the preceding factor’s alias can never
swallow it (unlike the keyword-led asof_join pair). On for
the MSSQL preset and Lenient (the permissive superset), off elsewhere: the other
engines parse-reject APPLY in join position.
recursive_search_cycle: boolAccept the SQL:2023 recursive-query SEARCH { DEPTH | BREADTH } FIRST BY … SET …
and CYCLE … SET … [TO … DEFAULT …] USING … clauses on a CTE
(Cte::search/cycle), written after the
CTE body’s ). One flag gates both clauses because PostgreSQL ships them as a
single recursive-query unit (the straight_join
paired-flag doctrine) — no dialect admits one without the other. When off, the
SEARCH/CYCLE keyword after a CTE body is left unconsumed and the statement is a
clean parse error. On for PostgreSQL / Lenient; off for ANSI (the conservative
baseline, like unnest), MySQL/SQLite (no such clauses), and
DuckDB — which parse-rejects both (syntax error at or near "SEARCH", probed on
1.5.4), so it overrides the PostgreSQL surface it otherwise inherits (the
MutationSyntax::data_modifying_ctes split precedent).
recursive_union_rejects_order_limit: boolParse-reject a top-level ORDER BY / LIMIT / OFFSET on a recursive CTE whose
body is a UNION [ALL] set operation (WITH RECURSIVE t AS (SELECT … UNION ALL SELECT … FROM t ORDER BY …)). DuckDB special-cases the recursive term’s grammar:
once WITH RECURSIVE fronts a UNION-bodied CTE the query is a recursive query,
and a result-shaping modifier on it is a parse error (Parser Error: ORDER BY in a recursive query is not allowed / LIMIT or OFFSET in a recursive query is not allowed; probed on 1.5.4). “order_limit” names the whole modifier set — ORDER BY,
LIMIT, and OFFSET — because DuckDB forbids them under one rule (no dialect admits
one but not the others), so it is one behaviour, not three.
Three boundaries are load-bearing and engine-probed (1.5.4), so the check mirrors
them exactly rather than firing on the bare RECURSIVE keyword: the body must be a
UNION set operation (a non-set-op recursive CTE, or an INTERSECT/EXCEPT body,
keeps its modifiers — those are not recursive-eligible); the modifier must sit on the
set operation itself, not a parenthesized arm or a nested subquery (((SELECT … LIMIT 1) UNION ALL …) and … WHERE x < (SELECT … ORDER BY 1) both accept); and
self-reference is not required (DuckDB rejects even a UNION whose right arm never
names the CTE — the check is syntactic). On for DuckDB only; every other preset
parse-accepts the modifier (PostgreSQL admits it outright, the rest defer the
restriction to binding), so it stays off there and the modifier rides the ordinary
query tail.
recursive_using_key: boolAccept DuckDB’s USING KEY (col, ...) recursive-CTE key clause
(Cte::using_key), written between the CTE column list and AS
(WITH RECURSIVE t(a, b) USING KEY (a) AS (…)). DuckDB’s keyed-recursion variant
(stable since 1.3; probed accepting on 1.5.4): the recurring table becomes a
key-indexed dictionary whose rows the recursive term overwrites in place. A distinct
gate from recursive_search_cycle — a different
engine (DuckDB, not PostgreSQL) and a different grammar slot (before AS, not after
the body’s )), so neither implies the other. Positioned ahead of AS, the leading
USING cannot be swallowed by any prior production (a CTE otherwise expects AS
next), so enabling it shadows no existing spelling. On for DuckDB / Lenient (the
permissive superset); off elsewhere, where the clause is a parse error.
Implementations§
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Sourcepub const DATABRICKS: Self
Available on crate feature databricks only.
pub const DATABRICKS: Self
databricks only.The DATABRICKS preset for join syntax.
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Source§impl JoinSyntax
impl JoinSyntax
Trait Implementations§
Source§impl Clone for JoinSyntax
impl Clone for JoinSyntax
Source§fn clone(&self) -> JoinSyntax
fn clone(&self) -> JoinSyntax
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more