Commit f705c04
authored
Lateral pg19 (#13)
* ext: enable LATERAL — drop gate, plumb NL nest-params, block correlated commute
PG-style LATERAL in the FROM clause now goes through ORCA instead of falling
back to the standard planner.
Three changes are needed together to make this work:
1. gpopt/translate/CTranslatorQueryToDXL.cpp
Remove the blanket "LATERAL unsupported" raise. Outer references from a
LATERAL RTE's inner Query already resolve via the parent translator's
CMappingVarColId (initialised in the constructor) — no separate plumbing
needed for the Var-to-ColRef mapping.
2. libgpopt/src/translate/CTranslatorExprToDXL.cpp (PdxlnNLJoin)
Generalise the existing DPE-PartitionSelector branch that turns a NL into
an IndexNLJ when the inner side has outer refs to the outer child's
output. Any inner subtree whose outer references intersect the outer
child's output now goes the same route: outer_refs get registered in
m_phmcrdxlnIndexLookup so the inner scalar translator emits a resolved
Ident, and the NL emits PARAM_EXEC nest params (under
EopttraceIndexedNLJOuterRefAsParams, which pg_orca enables by default).
Also drops the GPOS_ASSERT_IMP that forbade outer refs in non-index NL
inner children — that condition is now legal.
3. libgpopt/src/xforms/CXformInnerJoinCommutativity.{cpp,h}
Tighten the xform's promise: refuse to commute an InnerJoin when one
child's outer references reach into the other child's output columns. A
plain CLogicalInnerJoin is symmetric in ORCA's algebra, so without this
guard the join enumerator happily produced the swapped orientation for a
LATERAL-shaped join. Putting the correlated side on the NL outer is
unexecutable: the executor opens outer first and the outer-ref columns
are still unbound.
After this commit the six LATERAL shapes (uncorrelated, equi-correlated,
constant projection, TVF outer-ref arg, LIMIT, LEFT JOIN LATERAL) all
optimise to a correct ORCA plan, though equi-correlated cases still emit
NL+Materialize rather than HashJoin — that decorrelation comes in the
follow-up commit.
* ext: lower equi-correlated LATERAL to Apply for HashJoin decorrelation
LATERAL with a top-level CLogicalSelect on the right side (the typical
"FROM x, LATERAL (SELECT ... WHERE inner = x.col)" pattern) was being
turned into a plain CLogicalInnerJoin, which forced the optimizer onto
the NL+Materialize path. PG's own planner pulls these LATERALs up into
a HashJoin; ORCA can do the same once the algebra is right.
libgpopt/src/translate/CTranslatorDXLToExpr.cpp (PexprLogicalJoin)
Heuristic gate: if the right child's top operator is a CLogicalSelect
whose outer references intersect the left child's output, rebuild the
join as CLogicalInnerApply / CLogicalLeftOuterApply instead of a plain
Join. CXformInnerApply2InnerJoin / CXformLeftOuterApply2LeftOuterJoin
then pull the correlated predicate out of the Select and lower the
result to a Join, which the cost model picks as HashJoin.
Restricting to the "top is CLogicalSelect" shape keeps the other
LATERAL forms (TVF outer-ref args, LIMIT/Sort, constant projection)
on the plain-Join path, where the previous commit's commutativity
guard handles them via NL + nest params.
libgpopt/include/gpopt/xforms/CXformApply2Join.h (CreateCorrelatedApply)
Guard against TApply::PdrgPcrInner() == nullptr. Apply objects built
from LATERAL have no inner scalar colref (LATERAL returns a relation,
not a scalar), so the scalar-subquery-shaped correlated-apply form
here doesn't apply — no-op instead of dereferencing the null pointer.
Effect on the six LATERAL cases: equi-correlated inner-LATERAL drops
from NL+Materialize to a clean HashJoin matching PG; LEFT LATERAL drops
to HashRightJoin (still carries a triplicated Hash Cond pending the
LOJ inferred-pred dedup in the next commit). Other shapes unchanged.
* orca: dedup inferred predicates on LeftOuterJoin
PexprInferPredicates extends a join's predicate with extras derived from
constraint propagation (e.g. the commuted form of an equality, transitive
closures, etc). After predicate push-through, MakeJoinWithoutInferredPreds
strips the redundant ones back out via PexprRemoveImpliedConjuncts, keyed
on equivalence classes.
CanRemoveInferredPredicates was hard-coded to InnerJoin only — left over
from when LOJ semantics were considered too tricky to dedupe. The original
note "currently, only inner join is included, but we can add more later"
acknowledged the limitation. LeftOuterJoin's null-preserving side cares
about which qualifying tuples pair up, not about how many copies of an
equivalent equality predicate the matcher evaluates, so the dedup is
semantically safe.
Symptom: LEFT JOIN LATERAL (... WHERE inner = outer.col) produced a
HashRightJoin whose Hash Cond was a 3-way AND of equivalent equalities:
Hash Cond: ((lt2.a = lt1.x) AND (lt2.a = lt1.x) AND (lt2.a = lt1.x))
Three forms entered the predicate during preprocessing — original a=x,
commuted x=a from constraint inference, and a re-pushed copy — and the
LOJ branch of MakeJoinWithoutInferredPreds was a no-op. After this
commit the predicate is the single (lt2.a = lt1.x).
* orca: preserve sibling-correlated outer refs during computed-col pruning
PexprPruneUnusedComputedColsRecursive walks the expression top-down with
a required-columns set, dropping CScalarProjectElements that nothing
upstream consumes. The required set was built from each operator's own
PcrsLocalUsed and its scalar children's used columns (via
CExpressionHandle::PcrsUsedColumns) — neither of which captures outer
references that one relational child holds against another sibling.
For a LATERAL whose inner references a computed column from a derived
table on the outer side, the chain is:
LogicalApply / LogicalJoin
├── LogicalProject(dv = val * 2)
│ └── LogicalGet(na)
└── LogicalSelect(filter: nb.id = dv)
└── LogicalGet(nb)
The inner Select's DeriveOuterReferences() = {dv}, but the Apply's
PcrsUsedColumns() returns only the columns from the scalar predicate
(true) and PcrsLocalUsed (empty). The pruner descends into the outer
Project with `dv` absent from pcrsReqd → defined - required = {dv} →
the Project gets stripped. The dangling CScalarIdent "dv" then crashes
DXL→PlStmt translation with "Attribute number N not found".
Fix: before recursing into children, fold each relational child's
DeriveOuterReferences() into pcrsReqd. Those refs are columns the child
needs from its siblings, so siblings' producers must be preserved.
Includes outer refs that escape this operator entirely (genuine refs to
the grandparent) — those just stay in pcrsReqd as we descend; they have
no producer at this level and pruning logic only acts on Project /
GbAgg defined columns, so the extra entries are harmless.
Symptom: `SELECT count(*) FROM (SELECT id, val*2 AS dv FROM t) a,
LATERAL (SELECT * FROM s WHERE s.id = a.dv) x` fell back to PG with
"DXL-to-PlStmt Translation: Attribute number 8 not found in project list".
After this commit the query lowers to a clean HashJoin under ORCA.
* test: lock in LATERAL coverage with 18 cases
Adds a dedicated lateral.sql / lateral.out regression test under
test/schedule covering the six base LATERAL shapes (uncorrelated,
equi-correlated, scalar projection, TVF outer-ref arg, LIMIT, LEFT
JOIN LATERAL) plus twelve nested / composite variants:
- 2-level and 3-level chains (each LATERAL references its immediate
outer; or one LATERAL references both outer and middle)
- LATERAL nested inside another LATERAL
- LATERAL containing an inner JOIN (decorrelates to HashJoin chain)
- LEFT JOIN LATERAL with a nested LATERAL + LIMIT
- LATERAL containing an aggregate
- LATERAL with non-equi range correlation
- 3-level chain ending in a TVF
- LATERAL inside EXISTS
- LEFT LATERAL with an inner filter that excludes everything
- LATERAL referencing a derived-table computed column under an
aggregate (regression for the sibling-correlated outer-ref pruning
bug fixed in PexprPruneUnusedComputedColsRecursive)
The expected file pins the actual plan shape (Hash Join / Hash Right
Join / Function Scan / NL with nest params, etc.), so any future
change that regresses the commutativity guard, the selective Apply
conversion, the LOJ inferred-pred dedup, or the preprocessor outer-
ref preservation will diff visibly. Three back-to-back fresh-instance
runs show the plans are deterministic at ~150 ms.
* orca: PopCopyWithRemappedColumns nullptr guard for LATERAL Apply
CLogicalInnerApply / CLogicalLeftOuterApply built from LATERAL have no
inner scalar colref (LATERAL returns a relation, not a scalar), so
m_pdrgpcrInner is nullptr. PopCopyWithRemappedColumns blindly called
CUtils::PdrgpcrRemap on it, which dereferences the null array in
Release builds and SIGSEGVs (Debug builds catch it at the assert).
The crash is reached whenever ORCA needs to deep-copy a LATERAL-derived
Apply with column remapping, e.g. when a CLogicalCTEConsumer inlines a
producer whose body contains the Apply:
WITH t AS (SELECT * FROM a, LATERAL (SELECT * FROM b WHERE ...) s)
SELECT count(*) FROM t;
Guard against nullptr m_pdrgpcrInner and rebuild the copy with the
1-arg ctor (the 2-arg form asserts pdrgpcrInner is non-null+non-empty).
Found by walking the LATERAL edge-case matrix; lateral.sql now covers
this shape under E2.
* test: extend lateral.sql with 12 edge cases
Adds an "Edge cases" section to lateral.sql covering shapes that came
out of an LATERAL edge-case sweep:
E1 varlevelsup=2: LATERAL nested in a correlated scalar subquery
E2 LATERAL inside a CTE body (locks in the InnerApply nullptr-
PdrgPcrInner copy-with-remap crash that this commit pairs with)
E3 VALUES + LATERAL
E4 LATERAL + GROUP BY at outer
E5 LATERAL + GROUPING SETS
E6 LATERAL + window function
E7 UNION ALL inside the LATERAL body
E8 LATERAL with DISTINCT outside
E9 LATERAL top-N per outer (ORDER BY ... LIMIT 1)
E10 INSERT...SELECT with LATERAL
E11 3-level LATERAL where the grandchild references the outermost
E12 LATERAL unnest(array)
All twelve go through ORCA without fallback. Three back-to-back
fresh-instance runs settle at ~211 ms; the plan shapes are stable.
Edge cases that intentionally do NOT land here:
- CTE inside a LATERAL body -> pre-existing ORCA limitation
"Operator CTE with outer references not supported"
- PREPARE/EXECUTE with $params -> pre-existing limitation
(requires optimizer_enable_query_parameter, not wired in pg_orca)
- FOR UPDATE on an aggregate query -> SQL-level rejection
* test: import PG join.sql LATERAL section as pg_lateral regression
Adds test/sql/pg_lateral.sql — a verbatim port of the "Test LATERAL"
block from PostgreSQL upstream's src/test/regress/sql/join.sql (the
chunk introduced by the "-- Test LATERAL" header), plus inline setup
for int2_tbl / int4_tbl / int8_tbl / tenk1 / onerow mirroring upstream's
test_setup.sql and the top of join.sql. tenk1's 10000-row payload is
loaded via COPY from $PG_REGRESS_SQL/data/tenk.data so the test stays
in sync with PG when that file changes.
Locks in roughly 95 queries covering:
- basic equi-correlated LATERAL with tenk1 / int4_tbl / int8_tbl
- lateral-versus-parent scope resolution (the int8_tbl q1/q2 case)
- LATERAL with TVF args, UNION ALL, VALUES, GROUPING SETS-adjacent
aggregates, JOIN inside LATERAL
- lateral references requiring pullup at outer-join boundaries
- PlaceHolderVar nesting and the bug #9041 postponed-quals case
- dummy/empty inner rels (bug #15694)
- LATERAL with VALUES tuple containing outer refs to both sides of
an enclosing LEFT JOIN
- intentional SQL-level rejections (missing LATERAL keyword,
RIGHT/FULL JOIN with LATERAL, ambiguous column refs, UPDATE/DELETE
LATERAL restrictions)
Current ORCA coverage on this set: 15 queries optimised by ORCA (visible
Optimizer: pg_orca marker), 22 queries fall back to PG, all correctness
preserved. Fallback breakdown:
- 14 "DXL-to-PlStmt Translation: Attribute number N not found in
project list" — same class as the sibling-correlated outer-ref bug
fixed in CExpressionPreprocessor, but in more complex shapes
involving LeftOuter + LATERAL VALUES referencing both sides of
the outer join; ORCA's enumerator places the LATERAL on a side that
can't see one of its referenced columns at execution time
- 2 "Whole-row variable" (pre-existing ORCA limitation, e.g.
coalesce(i) on a record type)
- 2 "no plan has been computed for required properties" (enum gap)
- 4 intentional PG-side SQL rejections
Three back-to-back fresh-instance runs land at ~370 ms with stable
plans. Future fixes to the still-falling-back patterns will show up as
fewer fallback INFO lines and more Optimizer: pg_orca markers in the
expected output diff.
Statement timeout of 20s guards against ORCA picking a pathologically
bad plan that would otherwise hang the whole regression suite.
* orca: enforce LATERAL sibling visibility in DPv2 join enumeration
CJoinOrderDPv2 enumerates join orders by combining subsets of the
NAryJoin's atoms via dynamic programming. It tracked LOJ right-child
dependencies (an LOJ's right side must be paired with its left), but
did not track the more general LATERAL-style dependency where one
atom holds outer references to another sibling atom's output columns.
Without this check the enumerator happily formed subsets like
{x, lateral_ref_to_y} from a query
SELECT * FROM
int8_tbl x LEFT JOIN (SELECT q1, coalesce(q2,0) q2 FROM int8_tbl) y
ON x.q2 = y.q1,
LATERAL (VALUES (x.q1, y.q1, y.q2)) v(xq1, yq1, yq2);
The chosen physical plan placed the LATERAL VALUES inside a Nested
Loop whose outer was just x — but the VALUES references colids
produced by y, which is on a different side of the enclosing
LeftOuter. At execution time those refs are unbound; DXL→PlStmt
catches it as "Attribute number N not found in project list" and
falls back to the PG planner.
Precompute per-atom sibling requirements in the DPv2 constructor:
outer_refs_i = atom_i.DeriveOuterReferences()
sibling_refs = outer_refs_i − m_outer_refs // refs to NAryJoin
// siblings, not refs
// escaping the join
sibling_required[i] = { j | sibling_refs ∩ atom_j.DeriveOutputColumns() }
In GetJoinExpr, reject any candidate join whose combined atom set is
missing a required sibling of one of its members. The DP table then
never enumerates the unexecutable subset, and the LATERAL atom can
only enter the join once all its required siblings are already in.
Effect on test/sql/pg_lateral.sql (the PostgreSQL upstream LATERAL
section ported over): fallbacks drop from 22 to 12, ORCA-handled
EXPLAINs go from 15 to 17, and the "DXL-to-PlStmt Translation:
Attribute number N not found" pattern from this query shape
disappears. Other ORCA tests, the PG --pg-tests suite, and
cost_align.sh are unchanged.
Three back-to-back fresh-instance pg_lateral runs land at ~358 ms
with deterministic plans.
* test: relock pg_lateral expected — 10 fewer fallbacks after DPv2 fix
Re-captures test/expected/pg_lateral.out after the DPv2 sibling-
visibility enforcement. Bottom-line change:
fallback INFO lines: 22 -> 12 (-10)
"Optimizer: pg_orca" markers: 15 -> 17 (+2)
The "DXL-to-PlStmt Translation: Attribute number N not found in
project list" class of failure that came from the LeftOuter +
LATERAL VALUES atom-subset bug is now gone. Remaining 12 fallbacks
are unrelated patterns (Whole-row variable, "no plan computed for
required properties", and the four intentional PG-side SQL errors
from upstream's join.sql).1 parent 31afd0c commit f705c04
19 files changed
Lines changed: 12724 additions & 36 deletions
File tree
- gpopt/translate
- libgpopt
- include/gpopt/xforms
- src
- base
- operators
- translate
- xforms
- test
- data
- expected
- sql
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3269 | 3269 | | |
3270 | 3270 | | |
3271 | 3271 | | |
3272 | | - | |
3273 | | - | |
3274 | | - | |
3275 | | - | |
3276 | | - | |
| 3272 | + | |
| 3273 | + | |
| 3274 | + | |
| 3275 | + | |
| 3276 | + | |
| 3277 | + | |
3277 | 3278 | | |
3278 | 3279 | | |
3279 | 3280 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
441 | 441 | | |
442 | 442 | | |
443 | 443 | | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
444 | 452 | | |
445 | 453 | | |
446 | 454 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
71 | 82 | | |
72 | 83 | | |
73 | 84 | | |
| |||
77 | 88 | | |
78 | 89 | | |
79 | 90 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | 91 | | |
86 | 92 | | |
87 | 93 | | |
| |||
Lines changed: 1 addition & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
| 60 | + | |
66 | 61 | | |
67 | 62 | | |
68 | 63 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4928 | 4928 | | |
4929 | 4929 | | |
4930 | 4930 | | |
4931 | | - | |
| 4931 | + | |
| 4932 | + | |
4932 | 4933 | | |
4933 | 4934 | | |
4934 | 4935 | | |
| |||
5047 | 5048 | | |
5048 | 5049 | | |
5049 | 5050 | | |
5050 | | - | |
5051 | 5051 | | |
5052 | 5052 | | |
5053 | 5053 | | |
5054 | | - | |
| 5054 | + | |
| 5055 | + | |
5055 | 5056 | | |
5056 | 5057 | | |
5057 | 5058 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2486 | 2486 | | |
2487 | 2487 | | |
2488 | 2488 | | |
| 2489 | + | |
| 2490 | + | |
| 2491 | + | |
| 2492 | + | |
| 2493 | + | |
| 2494 | + | |
| 2495 | + | |
| 2496 | + | |
| 2497 | + | |
| 2498 | + | |
| 2499 | + | |
| 2500 | + | |
| 2501 | + | |
| 2502 | + | |
| 2503 | + | |
| 2504 | + | |
| 2505 | + | |
2489 | 2506 | | |
2490 | 2507 | | |
2491 | 2508 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
114 | 122 | | |
115 | 123 | | |
116 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
112 | 119 | | |
113 | 120 | | |
114 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
40 | 41 | | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
| |||
2094 | 2096 | | |
2095 | 2097 | | |
2096 | 2098 | | |
| 2099 | + | |
| 2100 | + | |
| 2101 | + | |
| 2102 | + | |
| 2103 | + | |
| 2104 | + | |
| 2105 | + | |
| 2106 | + | |
| 2107 | + | |
| 2108 | + | |
| 2109 | + | |
| 2110 | + | |
| 2111 | + | |
| 2112 | + | |
| 2113 | + | |
| 2114 | + | |
| 2115 | + | |
| 2116 | + | |
| 2117 | + | |
| 2118 | + | |
| 2119 | + | |
| 2120 | + | |
| 2121 | + | |
| 2122 | + | |
| 2123 | + | |
| 2124 | + | |
| 2125 | + | |
| 2126 | + | |
| 2127 | + | |
| 2128 | + | |
| 2129 | + | |
| 2130 | + | |
| 2131 | + | |
| 2132 | + | |
| 2133 | + | |
| 2134 | + | |
2097 | 2135 | | |
2098 | 2136 | | |
2099 | 2137 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4513 | 4513 | | |
4514 | 4514 | | |
4515 | 4515 | | |
4516 | | - | |
4517 | | - | |
4518 | | - | |
4519 | | - | |
4520 | | - | |
4521 | | - | |
4522 | | - | |
4523 | | - | |
4524 | | - | |
4525 | | - | |
4526 | | - | |
4527 | | - | |
4528 | | - | |
4529 | | - | |
4530 | | - | |
4531 | | - | |
| 4516 | + | |
| 4517 | + | |
| 4518 | + | |
| 4519 | + | |
| 4520 | + | |
4532 | 4521 | | |
4533 | 4522 | | |
4534 | 4523 | | |
| |||
4591 | 4580 | | |
4592 | 4581 | | |
4593 | 4582 | | |
| 4583 | + | |
| 4584 | + | |
| 4585 | + | |
| 4586 | + | |
| 4587 | + | |
| 4588 | + | |
| 4589 | + | |
| 4590 | + | |
| 4591 | + | |
| 4592 | + | |
| 4593 | + | |
| 4594 | + | |
| 4595 | + | |
| 4596 | + | |
| 4597 | + | |
| 4598 | + | |
| 4599 | + | |
| 4600 | + | |
| 4601 | + | |
| 4602 | + | |
| 4603 | + | |
| 4604 | + | |
| 4605 | + | |
| 4606 | + | |
| 4607 | + | |
| 4608 | + | |
| 4609 | + | |
| 4610 | + | |
| 4611 | + | |
| 4612 | + | |
| 4613 | + | |
| 4614 | + | |
| 4615 | + | |
| 4616 | + | |
| 4617 | + | |
| 4618 | + | |
4594 | 4619 | | |
4595 | 4620 | | |
4596 | 4621 | | |
| |||
0 commit comments