diff --git a/src/postgres/src/backend/optimizer/path/allpaths.c b/src/postgres/src/backend/optimizer/path/allpaths.c index d3ce4d545918..b8453d5032e4 100644 --- a/src/postgres/src/backend/optimizer/path/allpaths.c +++ b/src/postgres/src/backend/optimizer/path/allpaths.c @@ -1848,11 +1848,15 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, accumulate_append_subpath(subpath, &subpaths, NULL); } + subpaths_valid &= yb_has_same_batching_reqs(subpaths); + if (subpaths_valid) add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, required_outer, 0, false, partitioned_rels, -1)); + else + break; } } diff --git a/src/postgres/src/backend/optimizer/path/indxpath.c b/src/postgres/src/backend/optimizer/path/indxpath.c index 695d092c9ac5..285e2e19fcd6 100644 --- a/src/postgres/src/backend/optimizer/path/indxpath.c +++ b/src/postgres/src/backend/optimizer/path/indxpath.c @@ -716,9 +716,7 @@ yb_get_batched_index_paths(PlannerInfo *root, RelOptInfo *rel, batchedrelids = bms_difference(batchedrelids, unbatchablerelids); Assert(!root->yb_cur_batched_relids); - Assert(!root->yb_cur_unbatched_relids); root->yb_cur_batched_relids = batchedrelids; - root->yb_cur_unbatched_relids = unbatchablerelids; /* * Build simple index paths using the clauses. Allow ScalarArrayOpExpr @@ -775,7 +773,6 @@ yb_get_batched_index_paths(PlannerInfo *root, RelOptInfo *rel, } root->yb_cur_batched_relids = NULL; - root->yb_cur_unbatched_relids = NULL; } /* diff --git a/src/postgres/src/backend/optimizer/path/joinpath.c b/src/postgres/src/backend/optimizer/path/joinpath.c index 7336bfdcc469..98ec1369d750 100644 --- a/src/postgres/src/backend/optimizer/path/joinpath.c +++ b/src/postgres/src/backend/optimizer/path/joinpath.c @@ -451,17 +451,16 @@ try_nestloop_path(PlannerInfo *root, } } - if (inner_path->param_info && - inner_path->param_info->yb_ppi_req_outer_batched) + if (IsYugaByteEnabled() && YB_PATH_NEEDS_BATCHED_RELS(inner_path)) { if (yb_has_non_evaluable_bnl_clauses(outer_path, - inner_path, - extra->restrictlist) || - (yb_has_non_evaluable_bnl_clauses(outer_path, - inner_path, - inner_path->param_info - ->ppi_clauses))) + inner_path, + extra->restrictlist) || + (yb_has_non_evaluable_bnl_clauses(outer_path, + inner_path, + inner_path->param_info + ->ppi_clauses))) { bms_free(required_outer); return; @@ -1328,52 +1327,30 @@ generate_mergejoin_paths(PlannerInfo *root, Relids yb_get_batched_relids(NestPath *nest) { - ParamPathInfo *innerppi = nest->innerjoinpath->param_info; - ParamPathInfo *outerppi = nest->outerjoinpath->param_info; + Relids inner_batched = YB_PATH_REQ_OUTER_BATCHED(nest->innerjoinpath); - Relids outer_unbatched = - outerppi ? outerppi->yb_ppi_req_outer_unbatched : NULL; - Relids inner_batched = innerppi ? innerppi->yb_ppi_req_outer_batched : NULL; - - /* Rels not in this join that can't be batched. */ - Relids param_unbatched = - nest->path.param_info ? - nest->path.param_info->yb_ppi_req_outer_unbatched : NULL; - - return bms_difference(bms_difference(inner_batched, outer_unbatched), - param_unbatched); + Relids outerrels = nest->outerjoinpath->parent->relids; + return bms_intersect(inner_batched, outerrels); } Relids yb_get_unbatched_relids(NestPath *nest) { - ParamPathInfo *innerppi = nest->innerjoinpath->param_info; - ParamPathInfo *outerppi = nest->outerjoinpath->param_info; - - Relids outer_unbatched = - outerppi ? outerppi->yb_ppi_req_outer_unbatched : NULL; - Relids inner_unbatched = - innerppi ? innerppi->yb_ppi_req_outer_unbatched : NULL; + Relids outer_unbatched = YB_PATH_REQ_OUTER_UNBATCHED(nest->outerjoinpath); + Relids inner_unbatched = YB_PATH_REQ_OUTER_UNBATCHED(nest->innerjoinpath); /* Rels not in this join that can't be batched. */ - Relids param_unbatched = - nest->path.param_info ? - nest->path.param_info->yb_ppi_req_outer_unbatched : NULL; + Relids param_unbatched = YB_PATH_REQ_OUTER_UNBATCHED(&nest->path); return bms_union(outer_unbatched, - bms_union(inner_unbatched, param_unbatched)); + bms_union(inner_unbatched, param_unbatched)); } bool yb_is_outer_inner_batched(Path *outer, Path *inner) { - ParamPathInfo *innerppi = inner->param_info; - ParamPathInfo *outerppi = outer->param_info; - - Relids outer_unbatched = - outerppi ? outerppi->yb_ppi_req_outer_unbatched : NULL; - Relids inner_batched = - innerppi ? innerppi->yb_ppi_req_outer_batched : NULL; + Relids outer_unbatched = YB_PATH_REQ_OUTER_UNBATCHED(outer); + Relids inner_batched = YB_PATH_REQ_OUTER_BATCHED(inner); return bms_overlap(outer->parent->relids, bms_difference(inner_batched, outer_unbatched)); @@ -2127,7 +2104,7 @@ yb_has_non_evaluable_bnl_clauses(Path *outer_path, Path *inner_path, List *rinfos) { ListCell *lc; - Relids req_batched_rels = inner_path->param_info->yb_ppi_req_outer_batched; + Relids req_batched_rels = YB_PATH_REQ_OUTER_BATCHED(inner_path); Relids outer_relids = outer_path->parent->relids; Relids inner_relids = inner_path->parent->relids; diff --git a/src/postgres/src/backend/optimizer/plan/createplan.c b/src/postgres/src/backend/optimizer/plan/createplan.c index 02faed4b974a..7894eb3784ec 100644 --- a/src/postgres/src/backend/optimizer/plan/createplan.c +++ b/src/postgres/src/backend/optimizer/plan/createplan.c @@ -4871,6 +4871,11 @@ create_nestloop_plan(PlannerInfo *root, if (yb_is_batched) { + /* No rels supplied to inner from outer should be unbatched. */ + Relids inner_unbatched = + YB_PATH_REQ_OUTER_UNBATCHED(best_path->innerjoinpath); + Assert(!bms_overlap(inner_unbatched, outerrelids)); + (void)inner_unbatched; /* Add the available batched outer rels. */ root->yb_availBatchedRelids = lcons(outerrelids, root->yb_availBatchedRelids); @@ -4906,14 +4911,14 @@ create_nestloop_plan(PlannerInfo *root, } if (rinfo->can_join && - OidIsValid(rinfo->hashjoinoperator) && - yb_can_batch_rinfo(rinfo, batched_outerrelids, inner_relids)) + OidIsValid(rinfo->hashjoinoperator) && + yb_can_batch_rinfo(rinfo, batched_outerrelids, inner_relids)) { /* if nlhash can process this */ Assert(is_opclause(rinfo->clause)); RestrictInfo *batched_rinfo = - yb_get_batched_restrictinfo(rinfo,batched_outerrelids, - inner_relids); + yb_get_batched_restrictinfo(rinfo, batched_outerrelids, + inner_relids); hashOpno = ((OpExpr *) batched_rinfo->clause)->opno; } diff --git a/src/postgres/src/backend/optimizer/util/pathnode.c b/src/postgres/src/backend/optimizer/util/pathnode.c index ab9d6fd09ac3..164ba3504615 100644 --- a/src/postgres/src/backend/optimizer/util/pathnode.c +++ b/src/postgres/src/backend/optimizer/util/pathnode.c @@ -1295,14 +1295,14 @@ create_append_path(PlannerInfo *root, if (partitioned_rels != NIL && root && rel->reloptkind == RELOPT_BASEREL) { /* YB: Accumulate batching info from subpaths for this "baserel". */ - Assert(root->yb_cur_batched_relids == NULL); - yb_accumulate_batching_info(subpaths, - &root->yb_cur_batched_relids, &root->yb_cur_unbatched_relids); + Assert(yb_has_same_batching_reqs(subpaths)); + + root->yb_cur_batched_relids = + YB_PATH_REQ_OUTER_BATCHED((Path *) linitial(subpaths)); pathnode->path.param_info = get_baserel_parampathinfo(root, rel, required_outer); root->yb_cur_batched_relids = NULL; - root->yb_cur_unbatched_relids = NULL; } else pathnode->path.param_info = get_appendrel_parampathinfo(rel, @@ -2263,17 +2263,13 @@ create_nestloop_path(PlannerInfo *root, * because the restrict_clauses list can affect the size and cost * estimates for this path. */ - ParamPathInfo *param_info = inner_path->param_info; - Relids inner_req_batched = param_info == NULL - ? NULL : param_info->yb_ppi_req_outer_batched; - - Relids outer_req_unbatched = outer_path->param_info ? - outer_path->param_info->yb_ppi_req_outer_unbatched : - NULL; + Relids inner_req_batched = YB_PATH_REQ_OUTER_BATCHED(inner_path); + + Relids outer_req_unbatched = YB_PATH_REQ_OUTER_UNBATCHED(outer_path); bool is_batched = bms_overlap(inner_req_batched, - outer_path->parent->relids) && - !bms_overlap(outer_req_unbatched, inner_req_batched); + outer_path->parent->relids) && + !bms_overlap(outer_req_unbatched, inner_req_batched); if (!is_batched && bms_overlap(inner_req_outer, outer_path->parent->relids)) { Relids inner_and_outer = bms_union(inner_path->parent->relids, diff --git a/src/postgres/src/backend/optimizer/util/relnode.c b/src/postgres/src/backend/optimizer/util/relnode.c index 1221707a4dd5..58e69c876309 100644 --- a/src/postgres/src/backend/optimizer/util/relnode.c +++ b/src/postgres/src/backend/optimizer/util/relnode.c @@ -1247,7 +1247,6 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel, double rows; ListCell *lc; Relids batchedrelids = root->yb_cur_batched_relids; - Relids unbatchedrelids = root->yb_cur_unbatched_relids; /* If rel has LATERAL refs, every path for it should account for them */ Assert(bms_is_subset(baserel->lateral_relids, required_outer)); @@ -1263,9 +1262,8 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel, if ((ppi = yb_find_batched_param_path_info(baserel, required_outer, - batchedrelids, - unbatchedrelids))) - return ppi; + batchedrelids))) + return ppi; } else { @@ -1308,7 +1306,6 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel, ppi->ppi_rows = rows; ppi->ppi_clauses = pclauses; ppi->yb_ppi_req_outer_batched = batchedrelids; - ppi->yb_ppi_req_outer_unbatched = unbatchedrelids; baserel->ppilist = lappend(baserel->ppilist, ppi); @@ -1371,33 +1368,16 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, Assert(!bms_overlap(joinrel->relids, required_outer)); /* Compute batched and unbatched relids. */ - Relids outer_batchedrelids = outer_path->param_info - ? outer_path->param_info->yb_ppi_req_outer_batched - : NULL; - Relids inner_batchedrelids = inner_path->param_info - ? inner_path->param_info->yb_ppi_req_outer_batched - : NULL; - Relids outer_unbatchedrelids = outer_path->param_info - ? outer_path->param_info->yb_ppi_req_outer_unbatched - : NULL; - Relids inner_unbatchedrelids = inner_path->param_info - ? inner_path->param_info->yb_ppi_req_outer_unbatched - : NULL; - Assert(bms_is_subset(outer_batchedrelids, required_outer)); - - Relids req_batchedids = bms_union(outer_batchedrelids, - inner_batchedrelids); - req_batchedids = bms_difference(req_batchedids, - outer_path->parent->relids); + Relids req_batchedids = bms_union(YB_PATH_REQ_OUTER_BATCHED(outer_path), + YB_PATH_REQ_OUTER_BATCHED(inner_path)); - Relids req_unbatchedids = bms_union(outer_unbatchedrelids, - inner_unbatchedrelids); - - req_unbatchedids = bms_difference(req_unbatchedids, - outer_path->parent->relids); + Relids req_unbatchedids = + bms_union(YB_PATH_REQ_OUTER_UNBATCHED(outer_path), + YB_PATH_REQ_OUTER_UNBATCHED(inner_path)); req_batchedids = bms_difference(req_batchedids, - req_unbatchedids); + req_unbatchedids); + req_batchedids = bms_difference(req_batchedids, outer_path->parent->relids); /* * Identify all joinclauses that are movable to this join rel given this @@ -1535,9 +1515,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, } } - if (IsYugaByteEnabled() && - (!bms_is_empty(outer_batchedrelids) || - !bms_is_empty(inner_batchedrelids))) + if (IsYugaByteEnabled() && !bms_is_empty(req_batchedids)) { /* * YB: TODO: This can be omitted if we allow join filters to be batched @@ -1547,12 +1525,10 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, * This same logic can be applied to any mergejoinable qpqual within * a batched join context. */ - Relids outer_relids = outer_path->parent->relids; - Relids inner_relids = inner_path->parent->relids; foreach(lc, pclauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - /* + /* * YB: If outer/inner path has a relevant pclause that requires * external relids that are not in * outer/inner_batchedrelids @@ -1560,36 +1536,19 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, */ if (bms_is_subset(rinfo->clause_relids, joinrel->relids)) continue; - - Bitmapset *external_rels; - Bitmapset *unbatched_ext_rels; - if (bms_overlap(rinfo->clause_relids, outer_relids)) - { - external_rels = - bms_difference(rinfo->clause_relids, outer_relids); - unbatched_ext_rels = - bms_difference(external_rels, outer_batchedrelids); - req_unbatchedids = - bms_union(req_unbatchedids,unbatched_ext_rels); - } - else + if (bms_overlap(rinfo->clause_relids, req_batchedids)) { - Assert(bms_overlap(rinfo->clause_relids, inner_relids)); - external_rels = - bms_difference(rinfo->clause_relids, inner_relids); - unbatched_ext_rels = - bms_difference(external_rels, inner_batchedrelids); + Relids unbatched_ext_rels = + bms_difference(rinfo->clause_relids, joinrel->relids); req_unbatchedids = - bms_union(req_unbatchedids,unbatched_ext_rels); + bms_union(req_unbatchedids, unbatched_ext_rels); } - bms_free(external_rels); - bms_free(unbatched_ext_rels); } } - req_batchedids = bms_difference(req_batchedids, req_unbatchedids); + req_batchedids = bms_difference(req_batchedids, req_unbatchedids); /* * Now, attach the identified moved-down clauses to the caller's @@ -1601,8 +1560,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, /* If we already have a PPI for this parameterization, just return it */ if ((ppi = yb_find_batched_param_path_info(joinrel, required_outer, - req_batchedids, - req_unbatchedids))) + req_batchedids))) return ppi; /* Estimate the number of rows returned by the parameterized join */ @@ -1625,32 +1583,31 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, ppi->ppi_clauses = NIL; ppi->yb_ppi_req_outer_batched = req_batchedids; - ppi->yb_ppi_req_outer_unbatched = req_unbatchedids; joinrel->ppilist = lappend(joinrel->ppilist, ppi); return ppi; } -void -yb_accumulate_batching_info(List *paths, - Relids *batchedrelids, Relids *unbatchedrelids) +bool +yb_has_same_batching_reqs(List *paths) { + Path *first_path = (Path *) linitial(paths); + Relids first_req_outer_batch = YB_PATH_REQ_OUTER_BATCHED(first_path); + Relids first_req_outer = PATH_REQ_OUTER(first_path); ListCell *lc; foreach(lc, paths) { Path *path = (Path *) lfirst(lc); - ParamPathInfo *ppi = path->param_info; - if (ppi) - { - *batchedrelids = - bms_union(*batchedrelids, ppi->yb_ppi_req_outer_batched); - *unbatchedrelids = - bms_union(*unbatchedrelids, ppi->yb_ppi_req_outer_unbatched); - } + + if (!bms_equal(first_req_outer_batch, YB_PATH_REQ_OUTER_BATCHED(path))) + return false; + + if (!bms_equal(first_req_outer, PATH_REQ_OUTER(path))) + return false; } - *batchedrelids = bms_difference(*batchedrelids, *unbatchedrelids); + return true; } /* @@ -1693,8 +1650,7 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer) ParamPathInfo * yb_find_batched_param_path_info(RelOptInfo *rel, Relids required_outer, - Relids yb_required_batched_outer, - Relids yb_required_unbatched_outer) + Relids yb_required_batched_outer) { ListCell *lc; @@ -1703,10 +1659,8 @@ yb_find_batched_param_path_info(RelOptInfo *rel, Relids required_outer, ParamPathInfo *ppi = (ParamPathInfo *) lfirst(lc); if (bms_equal(ppi->ppi_req_outer, required_outer) && - bms_equal(ppi->yb_ppi_req_outer_batched, - yb_required_batched_outer) && - bms_equal(ppi->yb_ppi_req_outer_unbatched, - yb_required_unbatched_outer)) + bms_equal(ppi->yb_ppi_req_outer_batched, + yb_required_batched_outer)) return ppi; } diff --git a/src/postgres/src/include/nodes/relation.h b/src/postgres/src/include/nodes/relation.h index 31978df5af27..fcb967986f3c 100644 --- a/src/postgres/src/include/nodes/relation.h +++ b/src/postgres/src/include/nodes/relation.h @@ -1080,8 +1080,6 @@ typedef struct ParamPathInfo double ppi_rows; /* estimated number of result tuples */ List *ppi_clauses; /* join clauses available from outer rels */ Relids yb_ppi_req_outer_batched; /* outer rels that can be batched */ - Relids yb_ppi_req_outer_unbatched; /* outer rels that cannot - * be batched */ } ParamPathInfo; @@ -1142,6 +1140,16 @@ typedef struct Path #define PATH_REQ_OUTER(path) \ ((path)->param_info ? (path)->param_info->ppi_req_outer : (Relids) NULL) +#define YB_PATH_REQ_OUTER_BATCHED(path) \ + ((path)->param_info ? ((path)->param_info->yb_ppi_req_outer_batched) : \ + (Relids) NULL) + +#define YB_PATH_NEEDS_BATCHED_RELS(path) \ + !bms_is_empty(YB_PATH_REQ_OUTER_BATCHED(path)) + +#define YB_PATH_REQ_OUTER_UNBATCHED(path) \ + (bms_difference(PATH_REQ_OUTER(path), YB_PATH_REQ_OUTER_BATCHED(path))) + /*---------- * IndexPath represents an index scan over a single index. * diff --git a/src/postgres/src/include/optimizer/pathnode.h b/src/postgres/src/include/optimizer/pathnode.h index 0bb8eb2a6fdd..5ef49c1c656f 100644 --- a/src/postgres/src/include/optimizer/pathnode.h +++ b/src/postgres/src/include/optimizer/pathnode.h @@ -289,8 +289,7 @@ extern ParamPathInfo *get_joinrel_parampathinfo(PlannerInfo *root, SpecialJoinInfo *sjinfo, Relids required_outer, List **restrict_clauses); -extern void yb_accumulate_batching_info(List *paths, - Relids *batchedrelids, Relids *unbatchedrelids); +extern bool yb_has_same_batching_reqs(List *paths); extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer); extern ParamPathInfo *find_param_path_info(RelOptInfo *rel, @@ -298,8 +297,7 @@ extern ParamPathInfo *find_param_path_info(RelOptInfo *rel, extern ParamPathInfo *yb_find_batched_param_path_info( RelOptInfo *rel, Relids required_outer, - Relids yb_required_batched_outer, - Relids yb_required_unbatched_outer); + Relids yb_required_batched_outer); extern RelOptInfo *build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, diff --git a/src/postgres/src/test/regress/expected/yb_join_batching.out b/src/postgres/src/test/regress/expected/yb_join_batching.out index a2994cbbd250..d3015e1a8f5b 100644 --- a/src/postgres/src/test/regress/expected/yb_join_batching.out +++ b/src/postgres/src/test/regress/expected/yb_join_batching.out @@ -1454,6 +1454,52 @@ SELECT '' AS "xxx", t1.a, t2.e | 5 | -5 (7 rows) +CREATE TABLE x1 (a int PRIMARY KEY, b int); +CREATE INDEX i_x1_b ON x1 (b); +INSERT INTO x1 VALUES (1, 0), (2, 1); +CREATE TABLE x2 (a int PRIMARY KEY, b int); +CREATE INDEX i_x2_b ON x2 (b); +INSERT INTO x2 VALUES (1, 0), (2, 1); +CREATE TABLE x3 (a int PRIMARY KEY, b int); +CREATE INDEX i_x3_b ON x3 (b); +INSERT INTO x3 VALUES (1, 0), (2, 1); +EXPLAIN (COSTS OFF) SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY x1.a ASC; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: x1.a + -> Nested Loop Left Join + -> Seq Scan on x1 + -> Nested Loop Left Join + -> Index Scan using x2_pkey on x2 + Index Cond: (a = x1.a) + -> Index Scan using x3_pkey on x3 + Index Cond: (a = x1.a) + Remote Filter: (b = x2.b) +(10 rows) + +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY x1.a ASC; + a | b | a | b | b +---+---+---+---+--- + 1 | 0 | 1 | 0 | 0 + 2 | 1 | 2 | 1 | 1 +(2 rows) + +DROP TABLE x1; +DROP TABLE x2; +DROP TABLE x3; -- -- -- Inner joins (equi-joins) diff --git a/src/postgres/src/test/regress/sql/yb_join_batching.sql b/src/postgres/src/test/regress/sql/yb_join_batching.sql index 4bd71871ee80..967ea3cda6b4 100644 --- a/src/postgres/src/test/regress/sql/yb_join_batching.sql +++ b/src/postgres/src/test/regress/sql/yb_join_batching.sql @@ -387,6 +387,38 @@ SELECT '' AS "xxx", t1.a, t2.e FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) WHERE t1.a = t2.d order by 1, 2, 3; +CREATE TABLE x1 (a int PRIMARY KEY, b int); +CREATE INDEX i_x1_b ON x1 (b); +INSERT INTO x1 VALUES (1, 0), (2, 1); + +CREATE TABLE x2 (a int PRIMARY KEY, b int); +CREATE INDEX i_x2_b ON x2 (b); +INSERT INTO x2 VALUES (1, 0), (2, 1); + +CREATE TABLE x3 (a int PRIMARY KEY, b int); +CREATE INDEX i_x3_b ON x3 (b); +INSERT INTO x3 VALUES (1, 0), (2, 1); + +EXPLAIN (COSTS OFF) SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY x1.a ASC; + +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY x1.a ASC; + +DROP TABLE x1; +DROP TABLE x2; +DROP TABLE x3; + -- -- -- Inner joins (equi-joins)