diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2024-03-19 14:51:58 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2024-03-19 14:51:58 -0400 |
commit | b7e2121ab7d6166b835a46ceaab1b6a6dc589703 (patch) | |
tree | bd581b178710866309dbf52594f54cf9558ede50 | |
parent | 605721f819f5b603db6bc1405ef414747d182116 (diff) | |
download | postgresql-b7e2121ab7d6166b835a46ceaab1b6a6dc589703.tar.gz postgresql-b7e2121ab7d6166b835a46ceaab1b6a6dc589703.zip |
Postpone reparameterization of paths until create_plan().
When considering nestloop paths for individual partitions within
a partitionwise join, if the inner path is parameterized, it is
parameterized by the topmost parent of the outer rel, not the
corresponding outer rel itself. Therefore, we need to translate the
parameterization so that the inner path is parameterized by the
corresponding outer rel.
Up to now, we did this while generating join paths. However, that's
problematic because we must also translate some expressions that are
shared across all paths for a relation, such as restriction clauses
(kept in the RelOptInfo and/or IndexOptInfo) and TableSampleClauses
(kept in the RangeTblEntry). The existing code fails to translate
these at all, leading to wrong answers, odd failures such as
"variable not found in subplan target list", or executor crashes.
But we can't modify them during path generation, because that would
break things if we end up choosing some non-partitioned-join path.
So this patch postpones reparameterization of the inner path until
createplan.c, where it is safe to modify the referenced RangeTblEntry,
RelOptInfo or IndexOptInfo, because we have made a final choice of which
Path to use. We do still have to check during path generation that
the reparameterization will be possible. So we introduce a new
function path_is_reparameterizable_by_child() to detect that.
The duplication between path_is_reparameterizable_by_child() and
reparameterize_path_by_child() is a bit annoying, but there seems
no other good answer. A small benefit is that we can avoid building
useless reparameterized trees in cases where a non-partitioned join
is ultimately chosen. Also, reparameterize_path_by_child() can now
be allowed to scribble on the input paths, saving a few cycles.
This fix repairs the same problems previously addressed in the
back branches by commits 62f120203 et al.
Richard Guo, reviewed at various times by Ashutosh Bapat, Andrei
Lepikhov, Alena Rybakina, Robert Haas, and myself
Discussion: https://postgr.es/m/CAMbWs496+N=UAjOc=rcD3P7B6oJe4rZw08e_TZRUsWbPxZW3Tw@mail.gmail.com
-rw-r--r-- | src/backend/optimizer/path/joinpath.c | 67 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 17 | ||||
-rw-r--r-- | src/backend/optimizer/util/pathnode.c | 292 | ||||
-rw-r--r-- | src/include/optimizer/pathnode.h | 2 | ||||
-rw-r--r-- | src/test/regress/expected/partition_join.out | 168 | ||||
-rw-r--r-- | src/test/regress/sql/partition_join.sql | 40 |
6 files changed, 485 insertions, 101 deletions
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 6aca66f1962..5be8da9e095 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -30,8 +30,9 @@ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; /* - * Paths parameterized by the parent can be considered to be parameterized by - * any of its child. + * Paths parameterized by a parent rel can be considered to be parameterized + * by any of its children, when we are performing partitionwise joins. These + * macros simplify checking for such cases. Beware multiple eval of args. */ #define PATH_PARAM_BY_PARENT(path, rel) \ ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), \ @@ -785,6 +786,20 @@ try_nestloop_path(PlannerInfo *root, Assert(!have_unsafe_outer_join_ref(root, outerrelids, inner_paramrels)); /* + * If the inner path is parameterized, it is parameterized by the topmost + * parent of the outer rel, not the outer rel itself. We will need to + * translate the parameterization, if this path is chosen, during + * create_plan(). Here we just check whether we will be able to perform + * the translation, and if not avoid creating a nestloop path. + */ + if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent) && + !path_is_reparameterizable_by_child(inner_path, outer_path->parent)) + { + bms_free(required_outer); + return; + } + + /* * Do a precheck to quickly eliminate obviously-inferior paths. We * calculate a cheap lower bound on the path's cost and then use * add_path_precheck() to see if the path is clearly going to be dominated @@ -800,27 +815,6 @@ try_nestloop_path(PlannerInfo *root, workspace.startup_cost, workspace.total_cost, pathkeys, required_outer)) { - /* - * If the inner path is parameterized, it is parameterized by the - * topmost parent of the outer rel, not the outer rel itself. Fix - * that. - */ - if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent)) - { - inner_path = reparameterize_path_by_child(root, inner_path, - outer_path->parent); - - /* - * If we could not translate the path, we can't create nest loop - * path. - */ - if (!inner_path) - { - bms_free(required_outer); - return; - } - } - add_path(joinrel, (Path *) create_nestloop_path(root, joinrel, @@ -884,6 +878,17 @@ try_partial_nestloop_path(PlannerInfo *root, } /* + * If the inner path is parameterized, it is parameterized by the topmost + * parent of the outer rel, not the outer rel itself. We will need to + * translate the parameterization, if this path is chosen, during + * create_plan(). Here we just check whether we will be able to perform + * the translation, and if not avoid creating a nestloop path. + */ + if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent) && + !path_is_reparameterizable_by_child(inner_path, outer_path->parent)) + return; + + /* * Before creating a path, get a quick lower bound on what it is likely to * cost. Bail out right away if it looks terrible. */ @@ -892,22 +897,6 @@ try_partial_nestloop_path(PlannerInfo *root, if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys)) return; - /* - * If the inner path is parameterized, it is parameterized by the topmost - * parent of the outer rel, not the outer rel itself. Fix that. - */ - if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent)) - { - inner_path = reparameterize_path_by_child(root, inner_path, - outer_path->parent); - - /* - * If we could not translate the path, we can't create nest loop path. - */ - if (!inner_path) - return; - } - /* Might be good enough to be worth trying, so let's try it. */ add_partial_path(joinrel, (Path *) create_nestloop_path(root, diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 610f4a56d6b..5f479fc56c1 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -29,6 +29,7 @@ #include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/paramassign.h" +#include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" @@ -4355,6 +4356,22 @@ create_nestloop_plan(PlannerInfo *root, List *nestParams; Relids saveOuterRels = root->curOuterRels; + /* + * If the inner path is parameterized by the topmost parent of the outer + * rel rather than the outer rel itself, fix that. (Nothing happens here + * if it is not so parameterized.) + */ + best_path->jpath.innerjoinpath = + reparameterize_path_by_child(root, + best_path->jpath.innerjoinpath, + best_path->jpath.outerjoinpath->parent); + + /* + * Failure here probably means that reparameterize_path_by_child() is not + * in sync with path_is_reparameterizable_by_child(). + */ + Assert(best_path->jpath.innerjoinpath != NULL); + /* NestLoop can project, so no need to be picky about child tlists */ outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 246cd8f7476..0a7e5c2678f 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -52,6 +52,8 @@ static int append_startup_cost_compare(const ListCell *a, const ListCell *b); static List *reparameterize_pathlist_by_child(PlannerInfo *root, List *pathlist, RelOptInfo *child_rel); +static bool pathlist_is_reparameterizable_by_child(List *pathlist, + RelOptInfo *child_rel); /***************************************************************************** @@ -2463,6 +2465,16 @@ create_nestloop_path(PlannerInfo *root, { NestPath *pathnode = makeNode(NestPath); Relids inner_req_outer = PATH_REQ_OUTER(inner_path); + Relids outerrelids; + + /* + * Paths are parameterized by top-level parents, so run parameterization + * tests on the parent relids. + */ + if (outer_path->parent->top_parent_relids) + outerrelids = outer_path->parent->top_parent_relids; + else + outerrelids = outer_path->parent->relids; /* * If the inner path is parameterized by the outer, we must drop any @@ -2472,7 +2484,7 @@ create_nestloop_path(PlannerInfo *root, * estimates for this path. We detect such clauses by checking for serial * number match to clauses already enforced in the inner path. */ - if (bms_overlap(inner_req_outer, outer_path->parent->relids)) + if (bms_overlap(inner_req_outer, outerrelids)) { Bitmapset *enforced_serials = get_param_path_clause_serials(inner_path); List *jclauses = NIL; @@ -4072,34 +4084,39 @@ reparameterize_path(PlannerInfo *root, Path *path, * Given a path parameterized by the parent of the given child relation, * translate the path to be parameterized by the given child relation. * - * The function creates a new path of the same type as the given path, but - * parameterized by the given child relation. Most fields from the original - * path can simply be flat-copied, but any expressions must be adjusted to - * refer to the correct varnos, and any paths must be recursively - * reparameterized. Other fields that refer to specific relids also need - * adjustment. + * Most fields in the path are not changed, but any expressions must be + * adjusted to refer to the correct varnos, and any subpaths must be + * recursively reparameterized. Other fields that refer to specific relids + * also need adjustment. * * The cost, number of rows, width and parallel path properties depend upon - * path->parent, which does not change during the translation. Hence those - * members are copied as they are. + * path->parent, which does not change during the translation. So we need + * not change those. * * Currently, only a few path types are supported here, though more could be * added at need. We return NULL if we can't reparameterize the given path. + * + * Note that this function can change referenced RangeTblEntries, RelOptInfos + * and IndexOptInfos as well as the Path structures. Therefore, it's only safe + * to call during create_plan(), when we have made a final choice of which Path + * to use for each RangeTblEntry/RelOptInfo/IndexOptInfo. + * + * Keep this code in sync with path_is_reparameterizable_by_child()! */ Path * reparameterize_path_by_child(PlannerInfo *root, Path *path, RelOptInfo *child_rel) { - -#define FLAT_COPY_PATH(newnode, node, nodetype) \ - ( (newnode) = makeNode(nodetype), \ - memcpy((newnode), (node), sizeof(nodetype)) ) + Path *new_path; + ParamPathInfo *new_ppi; + ParamPathInfo *old_ppi; + Relids required_outer; #define ADJUST_CHILD_ATTRS(node) \ - ((node) = \ - (List *) adjust_appendrel_attrs_multilevel(root, (Node *) (node), \ - child_rel, \ - child_rel->top_parent)) + ((node) = (void *) adjust_appendrel_attrs_multilevel(root, \ + (Node *) (node), \ + child_rel, \ + child_rel->top_parent)) #define REPARAMETERIZE_CHILD_PATH(path) \ do { \ @@ -4119,21 +4136,16 @@ do { \ } \ } while(0) - Path *new_path; - ParamPathInfo *new_ppi; - ParamPathInfo *old_ppi; - Relids required_outer; - /* - * If the path is not parameterized by parent of the given relation, it - * doesn't need reparameterization. + * If the path is not parameterized by the parent of the given relation, + * it doesn't need reparameterization. */ if (!path->param_info || !bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids)) return path; /* - * If possible, reparameterize the given path, making a copy. + * If possible, reparameterize the given path. * * This function is currently only applied to the inner side of a nestloop * join that is being partitioned by the partitionwise-join code. Hence, @@ -4147,14 +4159,28 @@ do { \ switch (nodeTag(path)) { case T_Path: - FLAT_COPY_PATH(new_path, path, Path); + new_path = path; + ADJUST_CHILD_ATTRS(new_path->parent->baserestrictinfo); + if (path->pathtype == T_SampleScan) + { + Index scan_relid = path->parent->relid; + RangeTblEntry *rte; + + /* it should be a base rel with a tablesample clause... */ + Assert(scan_relid > 0); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + Assert(rte->tablesample != NULL); + + ADJUST_CHILD_ATTRS(rte->tablesample); + } break; case T_IndexPath: { - IndexPath *ipath; + IndexPath *ipath = (IndexPath *) path; - FLAT_COPY_PATH(ipath, path, IndexPath); + ADJUST_CHILD_ATTRS(ipath->indexinfo->indrestrictinfo); ADJUST_CHILD_ATTRS(ipath->indexclauses); new_path = (Path *) ipath; } @@ -4162,9 +4188,9 @@ do { \ case T_BitmapHeapPath: { - BitmapHeapPath *bhpath; + BitmapHeapPath *bhpath = (BitmapHeapPath *) path; - FLAT_COPY_PATH(bhpath, path, BitmapHeapPath); + ADJUST_CHILD_ATTRS(bhpath->path.parent->baserestrictinfo); REPARAMETERIZE_CHILD_PATH(bhpath->bitmapqual); new_path = (Path *) bhpath; } @@ -4172,9 +4198,8 @@ do { \ case T_BitmapAndPath: { - BitmapAndPath *bapath; + BitmapAndPath *bapath = (BitmapAndPath *) path; - FLAT_COPY_PATH(bapath, path, BitmapAndPath); REPARAMETERIZE_CHILD_PATH_LIST(bapath->bitmapquals); new_path = (Path *) bapath; } @@ -4182,9 +4207,8 @@ do { \ case T_BitmapOrPath: { - BitmapOrPath *bopath; + BitmapOrPath *bopath = (BitmapOrPath *) path; - FLAT_COPY_PATH(bopath, path, BitmapOrPath); REPARAMETERIZE_CHILD_PATH_LIST(bopath->bitmapquals); new_path = (Path *) bopath; } @@ -4192,10 +4216,10 @@ do { \ case T_ForeignPath: { - ForeignPath *fpath; + ForeignPath *fpath = (ForeignPath *) path; ReparameterizeForeignPathByChild_function rfpc_func; - FLAT_COPY_PATH(fpath, path, ForeignPath); + ADJUST_CHILD_ATTRS(fpath->path.parent->baserestrictinfo); if (fpath->fdw_outerpath) REPARAMETERIZE_CHILD_PATH(fpath->fdw_outerpath); if (fpath->fdw_restrictinfo) @@ -4213,9 +4237,9 @@ do { \ case T_CustomPath: { - CustomPath *cpath; + CustomPath *cpath = (CustomPath *) path; - FLAT_COPY_PATH(cpath, path, CustomPath); + ADJUST_CHILD_ATTRS(cpath->path.parent->baserestrictinfo); REPARAMETERIZE_CHILD_PATH_LIST(cpath->custom_paths); if (cpath->custom_restrictinfo) ADJUST_CHILD_ATTRS(cpath->custom_restrictinfo); @@ -4231,12 +4255,9 @@ do { \ case T_NestPath: { - JoinPath *jpath; - NestPath *npath; - - FLAT_COPY_PATH(npath, path, NestPath); + NestPath *npath = (NestPath *) path; + JoinPath *jpath = (JoinPath *) npath; - jpath = (JoinPath *) npath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); @@ -4246,12 +4267,9 @@ do { \ case T_MergePath: { - JoinPath *jpath; - MergePath *mpath; + MergePath *mpath = (MergePath *) path; + JoinPath *jpath = (JoinPath *) mpath; - FLAT_COPY_PATH(mpath, path, MergePath); - - jpath = (JoinPath *) mpath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); @@ -4262,12 +4280,9 @@ do { \ case T_HashPath: { - JoinPath *jpath; - HashPath *hpath; - - FLAT_COPY_PATH(hpath, path, HashPath); + HashPath *hpath = (HashPath *) path; + JoinPath *jpath = (JoinPath *) hpath; - jpath = (JoinPath *) hpath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); @@ -4278,9 +4293,8 @@ do { \ case T_AppendPath: { - AppendPath *apath; + AppendPath *apath = (AppendPath *) path; - FLAT_COPY_PATH(apath, path, AppendPath); REPARAMETERIZE_CHILD_PATH_LIST(apath->subpaths); new_path = (Path *) apath; } @@ -4288,9 +4302,8 @@ do { \ case T_MaterialPath: { - MaterialPath *mpath; + MaterialPath *mpath = (MaterialPath *) path; - FLAT_COPY_PATH(mpath, path, MaterialPath); REPARAMETERIZE_CHILD_PATH(mpath->subpath); new_path = (Path *) mpath; } @@ -4298,9 +4311,8 @@ do { \ case T_MemoizePath: { - MemoizePath *mpath; + MemoizePath *mpath = (MemoizePath *) path; - FLAT_COPY_PATH(mpath, path, MemoizePath); REPARAMETERIZE_CHILD_PATH(mpath->subpath); ADJUST_CHILD_ATTRS(mpath->param_exprs); new_path = (Path *) mpath; @@ -4309,16 +4321,14 @@ do { \ case T_GatherPath: { - GatherPath *gpath; + GatherPath *gpath = (GatherPath *) path; - FLAT_COPY_PATH(gpath, path, GatherPath); REPARAMETERIZE_CHILD_PATH(gpath->subpath); new_path = (Path *) gpath; } break; default: - /* We don't know how to reparameterize this path. */ return NULL; } @@ -4379,8 +4389,146 @@ do { \ } /* + * path_is_reparameterizable_by_child + * Given a path parameterized by the parent of the given child relation, + * see if it can be translated to be parameterized by the child relation. + * + * This must return true if and only if reparameterize_path_by_child() + * would succeed on this path. Currently it's sufficient to verify that + * the path and all of its subpaths (if any) are of the types handled by + * that function. However, subpaths that are not parameterized can be + * disregarded since they won't require translation. + */ +bool +path_is_reparameterizable_by_child(Path *path, RelOptInfo *child_rel) +{ +#define REJECT_IF_PATH_NOT_REPARAMETERIZABLE(path) \ +do { \ + if (!path_is_reparameterizable_by_child(path, child_rel)) \ + return false; \ +} while(0) + +#define REJECT_IF_PATH_LIST_NOT_REPARAMETERIZABLE(pathlist) \ +do { \ + if (!pathlist_is_reparameterizable_by_child(pathlist, child_rel)) \ + return false; \ +} while(0) + + /* + * If the path is not parameterized by the parent of the given relation, + * it doesn't need reparameterization. + */ + if (!path->param_info || + !bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids)) + return true; + + /* + * Check that the path type is one that reparameterize_path_by_child() can + * handle, and recursively check subpaths. + */ + switch (nodeTag(path)) + { + case T_Path: + case T_IndexPath: + break; + + case T_BitmapHeapPath: + { + BitmapHeapPath *bhpath = (BitmapHeapPath *) path; + + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(bhpath->bitmapqual); + } + break; + + case T_BitmapAndPath: + { + BitmapAndPath *bapath = (BitmapAndPath *) path; + + REJECT_IF_PATH_LIST_NOT_REPARAMETERIZABLE(bapath->bitmapquals); + } + break; + + case T_BitmapOrPath: + { + BitmapOrPath *bopath = (BitmapOrPath *) path; + + REJECT_IF_PATH_LIST_NOT_REPARAMETERIZABLE(bopath->bitmapquals); + } + break; + + case T_ForeignPath: + { + ForeignPath *fpath = (ForeignPath *) path; + + if (fpath->fdw_outerpath) + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(fpath->fdw_outerpath); + } + break; + + case T_CustomPath: + { + CustomPath *cpath = (CustomPath *) path; + + REJECT_IF_PATH_LIST_NOT_REPARAMETERIZABLE(cpath->custom_paths); + } + break; + + case T_NestPath: + case T_MergePath: + case T_HashPath: + { + JoinPath *jpath = (JoinPath *) path; + + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(jpath->outerjoinpath); + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(jpath->innerjoinpath); + } + break; + + case T_AppendPath: + { + AppendPath *apath = (AppendPath *) path; + + REJECT_IF_PATH_LIST_NOT_REPARAMETERIZABLE(apath->subpaths); + } + break; + + case T_MaterialPath: + { + MaterialPath *mpath = (MaterialPath *) path; + + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(mpath->subpath); + } + break; + + case T_MemoizePath: + { + MemoizePath *mpath = (MemoizePath *) path; + + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(mpath->subpath); + } + break; + + case T_GatherPath: + { + GatherPath *gpath = (GatherPath *) path; + + REJECT_IF_PATH_NOT_REPARAMETERIZABLE(gpath->subpath); + } + break; + + default: + /* We don't know how to reparameterize this path. */ + return false; + } + + return true; +} + +/* * reparameterize_pathlist_by_child * Helper function to reparameterize a list of paths by given child rel. + * + * Returns NIL to indicate failure, so pathlist had better not be NIL. */ static List * reparameterize_pathlist_by_child(PlannerInfo *root, @@ -4406,3 +4554,23 @@ reparameterize_pathlist_by_child(PlannerInfo *root, return result; } + +/* + * pathlist_is_reparameterizable_by_child + * Helper function to check if a list of paths can be reparameterized. + */ +static bool +pathlist_is_reparameterizable_by_child(List *pathlist, RelOptInfo *child_rel) +{ + ListCell *lc; + + foreach(lc, pathlist) + { + Path *path = (Path *) lfirst(lc); + + if (!path_is_reparameterizable_by_child(path, child_rel)) + return false; + } + + return true; +} diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index c43d97b48a6..99c2f955aab 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -298,6 +298,8 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path, double loop_count); extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, RelOptInfo *child_rel); +extern bool path_is_reparameterizable_by_child(Path *path, + RelOptInfo *child_rel); /* * prototypes for relnode.c diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 6560fe2416f..6d07f86b9bc 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -505,6 +505,98 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL 550 | | (12 rows) +-- lateral reference in sample scan +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 JOIN LATERAL + (SELECT * FROM prt1 t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s + ON t1.a = s.a; + QUERY PLAN +------------------------------------------------------------- + Append + -> Nested Loop + -> Seq Scan on prt1_p1 t1_1 + -> Sample Scan on prt1_p1 t2_1 + Sampling: system (t1_1.a) REPEATABLE (t1_1.b) + Filter: (t1_1.a = a) + -> Nested Loop + -> Seq Scan on prt1_p2 t1_2 + -> Sample Scan on prt1_p2 t2_2 + Sampling: system (t1_2.a) REPEATABLE (t1_2.b) + Filter: (t1_2.a = a) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + -> Sample Scan on prt1_p3 t2_3 + Sampling: system (t1_3.a) REPEATABLE (t1_3.b) + Filter: (t1_3.a = a) +(16 rows) + +-- lateral reference in scan's restriction clauses +EXPLAIN (COSTS OFF) +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.a; + QUERY PLAN +--------------------------------------------------------------- + Aggregate + -> Append + -> Nested Loop + -> Seq Scan on prt1_p1 t1_1 + -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 + Index Cond: (b = t1_1.a) + Filter: (t1_1.b = a) + -> Nested Loop + -> Seq Scan on prt1_p2 t1_2 + -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 + Index Cond: (b = t1_2.a) + Filter: (t1_2.b = a) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + Filter: (t1_3.b = a) +(17 rows) + +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.a; + count +------- + 100 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.b; + QUERY PLAN +-------------------------------------------------------------------- + Aggregate + -> Append + -> Nested Loop + -> Seq Scan on prt1_p1 t1_1 + -> Index Only Scan using iprt2_p1_b on prt2_p1 t2_1 + Index Cond: (b = t1_1.a) + Filter: (b = t1_1.b) + -> Nested Loop + -> Seq Scan on prt1_p2 t1_2 + -> Index Only Scan using iprt2_p2_b on prt2_p2 t2_2 + Index Cond: (b = t1_2.a) + Filter: (b = t1_2.b) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + -> Index Only Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + Filter: (b = t1_3.b) +(17 rows) + +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.b; + count +------- + 5 +(1 row) + -- bug with inadequate sort key representation SET enable_partitionwise_aggregate TO true; SET enable_hashjoin TO false; @@ -1944,6 +2036,82 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL 550 | 0 | 0002 | | | | | (12 rows) +-- partitionwise join with lateral reference in sample scan +EXPLAIN (COSTS OFF) +SELECT * FROM prt1_l t1 JOIN LATERAL + (SELECT * FROM prt1_l t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s + ON t1.a = s.a AND t1.b = s.b AND t1.c = s.c; + QUERY PLAN +---------------------------------------------------------------------------------------- + Append + -> Nested Loop + -> Seq Scan on prt1_l_p1 t1_1 + -> Sample Scan on prt1_l_p1 t2_1 + Sampling: system (t1_1.a) REPEATABLE (t1_1.b) + Filter: ((t1_1.a = a) AND (t1_1.b = b) AND ((t1_1.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p2_p1 t1_2 + -> Sample Scan on prt1_l_p2_p1 t2_2 + Sampling: system (t1_2.a) REPEATABLE (t1_2.b) + Filter: ((t1_2.a = a) AND (t1_2.b = b) AND ((t1_2.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p2_p2 t1_3 + -> Sample Scan on prt1_l_p2_p2 t2_3 + Sampling: system (t1_3.a) REPEATABLE (t1_3.b) + Filter: ((t1_3.a = a) AND (t1_3.b = b) AND ((t1_3.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p3_p1 t1_4 + -> Sample Scan on prt1_l_p3_p1 t2_4 + Sampling: system (t1_4.a) REPEATABLE (t1_4.b) + Filter: ((t1_4.a = a) AND (t1_4.b = b) AND ((t1_4.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p3_p2 t1_5 + -> Sample Scan on prt1_l_p3_p2 t2_5 + Sampling: system (t1_5.a) REPEATABLE (t1_5.b) + Filter: ((t1_5.a = a) AND (t1_5.b = b) AND ((t1_5.c)::text = (c)::text)) +(26 rows) + +-- partitionwise join with lateral reference in scan's restriction clauses +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s + ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c + WHERE s.t1b = s.a; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + Aggregate + -> Append + -> Nested Loop + -> Seq Scan on prt1_l_p1 t1_1 + -> Seq Scan on prt2_l_p1 t2_1 + Filter: ((a = t1_1.b) AND (t1_1.a = b) AND (t1_1.b = a) AND ((t1_1.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p2_p1 t1_2 + -> Seq Scan on prt2_l_p2_p1 t2_2 + Filter: ((a = t1_2.b) AND (t1_2.a = b) AND (t1_2.b = a) AND ((t1_2.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p2_p2 t1_3 + -> Seq Scan on prt2_l_p2_p2 t2_3 + Filter: ((a = t1_3.b) AND (t1_3.a = b) AND (t1_3.b = a) AND ((t1_3.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p3_p1 t1_4 + -> Seq Scan on prt2_l_p3_p1 t2_4 + Filter: ((a = t1_4.b) AND (t1_4.a = b) AND (t1_4.b = a) AND ((t1_4.c)::text = (c)::text)) + -> Nested Loop + -> Seq Scan on prt1_l_p3_p2 t1_5 + -> Seq Scan on prt2_l_p3_p2 t2_5 + Filter: ((a = t1_5.b) AND (t1_5.a = b) AND (t1_5.b = a) AND ((t1_5.c)::text = (c)::text)) +(22 rows) + +SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s + ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c + WHERE s.t1b = s.a; + count +------- + 100 +(1 row) + -- join with one side empty EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c; diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 48daf3aee39..128ce8376e6 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -100,6 +100,29 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; +-- lateral reference in sample scan +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 JOIN LATERAL + (SELECT * FROM prt1 t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s + ON t1.a = s.a; + +-- lateral reference in scan's restriction clauses +EXPLAIN (COSTS OFF) +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.a; +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.a; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.b; +SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s + ON t1.a = s.b WHERE s.t1b = s.b; + -- bug with inadequate sort key representation SET enable_partitionwise_aggregate TO true; SET enable_hashjoin TO false; @@ -387,6 +410,23 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; +-- partitionwise join with lateral reference in sample scan +EXPLAIN (COSTS OFF) +SELECT * FROM prt1_l t1 JOIN LATERAL + (SELECT * FROM prt1_l t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s + ON t1.a = s.a AND t1.b = s.b AND t1.c = s.c; + +-- partitionwise join with lateral reference in scan's restriction clauses +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s + ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c + WHERE s.t1b = s.a; +SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s + ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c + WHERE s.t1b = s.a; + -- join with one side empty EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c; |