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
This commit is contained in:
Tom Lane 2024-03-19 14:51:58 -04:00
parent 605721f819
commit b7e2121ab7
6 changed files with 485 additions and 101 deletions

View File

@ -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), \
@ -784,6 +785,20 @@ try_nestloop_path(PlannerInfo *root,
/* If we got past that, we shouldn't have any unsafe outer-join refs */
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
@ -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,
@ -883,6 +877,17 @@ try_partial_nestloop_path(PlannerInfo *root,
return;
}
/*
* 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,

View File

@ -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);

View File

@ -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;
NestPath *npath = (NestPath *) path;
JoinPath *jpath = (JoinPath *) npath;
FLAT_COPY_PATH(npath, path, NestPath);
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;
HashPath *hpath = (HashPath *) path;
JoinPath *jpath = (JoinPath *) hpath;
FLAT_COPY_PATH(hpath, path, HashPath);
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;
}
@ -4378,9 +4388,147 @@ do { \
return new_path;
}
/*
* 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;
}

View File

@ -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

View File

@ -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;

View File

@ -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;