Commit 8edd0e79 authored by Tom Lane's avatar Tom Lane

Suppress Append and MergeAppend plan nodes that have a single child.

If there's only one child relation, the Append or MergeAppend isn't
doing anything useful, and can be elided.  It does have a purpose
during planning though, which is to serve as a buffer between parent
and child Var numbering.  Therefore we keep it all the way through
to setrefs.c, and get rid of it only after fixing references in the
plan level(s) above it.  This works largely the same as setrefs.c's
ancient hack to get rid of no-op SubqueryScan nodes, and can even
share some code with that.

Note the change to make setrefs.c use apply_tlist_labeling rather than
ad-hoc code.  This has the effect of propagating the child's resjunk
and ressortgroupref labels, which formerly weren't propagated when
removing a SubqueryScan.  Doing that is demonstrably necessary for
the [Merge]Append cases, and seems harmless for SubqueryScan, if only
because trivial_subqueryscan is afraid to collapse cases where the
resjunk marking differs.  (I suspect that restriction could now be
removed, though it's unclear that it'd make any new matches possible,
since the outer query can't have references to a child resjunk column.)

David Rowley, reviewed by Alvaro Herrera and Tomas Vondra

Discussion: https://postgr.es/m/CAKJS1f_7u8ATyJ1JGTMHFoKDvZdeF-iEBhs+sM_SXowOr9cArg@mail.gmail.com
parent f21668f3
......@@ -8427,17 +8427,16 @@ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER J
400 | 400 | 0008
(4 rows)
-- left outer join + nullable clasue
EXPLAIN (COSTS OFF)
-- left outer join + nullable clause
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
QUERY PLAN
-----------------------------------------------------------------------------------
Sort
Sort Key: t1.a, ftprt2_p1.b, ftprt2_p1.c
-> Append
-> Foreign Scan
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan
Output: t1.a, ftprt2_p1.b, ftprt2_p1.c
Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2)
(5 rows)
Remote SQL: SELECT r6.a, r9.b, r9.c FROM (public.fprt1_p1 r6 LEFT JOIN public.fprt2_p1 r9 ON (((r6.a = r9.b)) AND ((r6.b = r9.a)) AND ((r9.a < 10)))) WHERE ((r6.a < 10)) ORDER BY r6.a ASC NULLS LAST, r9.b ASC NULLS LAST, r9.c ASC NULLS LAST
(4 rows)
SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
a | b | c
......
......@@ -2309,8 +2309,8 @@ EXPLAIN (COSTS OFF)
SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
-- left outer join + nullable clasue
EXPLAIN (COSTS OFF)
-- left outer join + nullable clause
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
......
......@@ -447,6 +447,36 @@ ExecSupportsMarkRestore(Path *pathnode)
return false; /* childless Result */
}
case T_Append:
{
AppendPath *appendPath = castNode(AppendPath, pathnode);
/*
* If there's exactly one child, then there will be no Append
* in the final plan, so we can handle mark/restore if the
* child plan node can.
*/
if (list_length(appendPath->subpaths) == 1)
return ExecSupportsMarkRestore((Path *) linitial(appendPath->subpaths));
/* Otherwise, Append can't handle it */
return false;
}
case T_MergeAppend:
{
MergeAppendPath *mapath = castNode(MergeAppendPath, pathnode);
/*
* Like the Append case above, single-subpath MergeAppends
* won't be in the final plan, so just return the child's
* mark/restore ability.
*/
if (list_length(mapath->subpaths) == 1)
return ExecSupportsMarkRestore((Path *) linitial(mapath->subpaths));
/* Otherwise, MergeAppend can't handle it */
return false;
}
default:
break;
}
......
......@@ -1044,8 +1044,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
/*
* We have to copy the parent's targetlist and quals to the child,
* with appropriate substitution of variables. If any constant false
* or NULL clauses turn up, we can disregard the child right away.
* If not, we can apply constraint exclusion with just the
* or NULL clauses turn up, we can disregard the child right away. If
* not, we can apply constraint exclusion with just the
* baserestrictinfo quals.
*/
if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo))
......@@ -1708,6 +1708,38 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
required_outer, 0, false,
partitioned_rels, -1));
}
/*
* When there is only a single child relation, the Append path can inherit
* any ordering available for the child rel's path, so that it's useful to
* consider ordered partial paths. Above we only considered the cheapest
* partial path for each child, but let's also make paths using any
* partial paths that have pathkeys.
*/
if (list_length(live_childrels) == 1)
{
RelOptInfo *childrel = (RelOptInfo *) linitial(live_childrels);
foreach(l, childrel->partial_pathlist)
{
Path *path = (Path *) lfirst(l);
AppendPath *appendpath;
/*
* Skip paths with no pathkeys. Also skip the cheapest partial
* path, since we already used that above.
*/
if (path->pathkeys == NIL ||
path == linitial(childrel->partial_pathlist))
continue;
appendpath = create_append_path(root, rel, NIL, list_make1(path),
NULL, path->parallel_workers,
true,
partitioned_rels, partial_rows);
add_partial_path(rel, (Path *) appendpath);
}
}
}
/*
......
......@@ -1134,10 +1134,10 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
}
/*
* XXX ideally, if there's just one child, we'd not bother to generate an
* Append node but just return the single child. At the moment this does
* not work because the varno of the child scan plan won't match the
* parent-rel Vars it'll be asked to emit.
* And build the Append plan. Note that if there's just one child, the
* Append is pretty useless; but we wait till setrefs.c to get rid of it.
* Doing so here doesn't work because the varno of the child scan plan
* won't match the parent-rel Vars it'll be asked to emit.
*/
plan = make_append(subplans, best_path->first_partial_path,
......
......@@ -94,12 +94,19 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
SubqueryScan *plan,
int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan);
static Plan *clean_up_removed_plan_level(Plan *parent, Plan *child);
static void set_foreignscan_references(PlannerInfo *root,
ForeignScan *fscan,
int rtoffset);
static void set_customscan_references(PlannerInfo *root,
CustomScan *cscan,
int rtoffset);
static Plan *set_append_references(PlannerInfo *root,
Append *aplan,
int rtoffset);
static Plan *set_mergeappend_references(PlannerInfo *root,
MergeAppend *mplan,
int rtoffset);
static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
......@@ -181,19 +188,22 @@ static List *set_returning_clause_references(PlannerInfo *root,
* 8. We assign every plan node in the tree a unique ID.
*
* We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
* no qual and a no-op targetlist). The reason for doing this last is that
* SubqueryScan, Append, and MergeAppend plan nodes that aren't doing
* anything useful. The reason for doing this last is that
* it can't readily be done before set_plan_references, because it would
* break set_upper_references: the Vars in the subquery's top tlist
* wouldn't match up with the Vars in the outer plan tree. The SubqueryScan
* break set_upper_references: the Vars in the child plan's top tlist
* wouldn't match up with the Vars in the outer plan tree. A SubqueryScan
* serves a necessary function as a buffer between outer query and subquery
* variable numbering ... but after we've flattened the rangetable this is
* no longer a problem, since then there's only one rtindex namespace.
* Likewise, Append and MergeAppend buffer between the parent and child vars
* of an appendrel, but we don't need to worry about that once we've done
* set_plan_references.
*
* set_plan_references recursively traverses the whole plan tree.
*
* The return value is normally the same Plan node passed in, but can be
* different when the passed-in Plan is a SubqueryScan we decide isn't needed.
* different when the passed-in Plan is a node we decide isn't needed.
*
* The flattened rangetable entries are appended to root->glob->finalrtable.
* Also, rowmarks entries are appended to root->glob->finalrowmarks, and the
......@@ -897,71 +907,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
}
break;
case T_Append:
{
Append *splan = (Append *) plan;
/*
* Append, like Sort et al, doesn't actually evaluate its
* targetlist or check quals.
*/
set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->appendplans)
{
lfirst(l) = set_plan_refs(root,
(Plan *) lfirst(l),
/* Needs special treatment, see comments below */
return set_append_references(root,
(Append *) plan,
rtoffset);
}
if (splan->part_prune_info)
{
foreach(l, splan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
}
break;
case T_MergeAppend:
{
MergeAppend *splan = (MergeAppend *) plan;
/*
* MergeAppend, like Sort et al, doesn't actually evaluate its
* targetlist or check quals.
*/
set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->mergeplans)
{
lfirst(l) = set_plan_refs(root,
(Plan *) lfirst(l),
/* Needs special treatment, see comments below */
return set_mergeappend_references(root,
(MergeAppend *) plan,
rtoffset);
}
if (splan->part_prune_info)
{
foreach(l, splan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
}
break;
case T_RecursiveUnion:
/* This doesn't evaluate targetlist or check quals either */
set_dummy_tlist_references(plan, rtoffset);
......@@ -1086,30 +1040,7 @@ set_subqueryscan_references(PlannerInfo *root,
/*
* We can omit the SubqueryScan node and just pull up the subplan.
*/
ListCell *lp,
*lc;
result = plan->subplan;
/* We have to be sure we don't lose any initplans */
result->initPlan = list_concat(plan->scan.plan.initPlan,
result->initPlan);
/*
* We also have to transfer the SubqueryScan's result-column names
* into the subplan, else columns sent to client will be improperly
* labeled if this is the topmost plan level. Copy the "source
* column" information too.
*/
forboth(lp, plan->scan.plan.targetlist, lc, result->targetlist)
{
TargetEntry *ptle = (TargetEntry *) lfirst(lp);
TargetEntry *ctle = (TargetEntry *) lfirst(lc);
ctle->resname = ptle->resname;
ctle->resorigtbl = ptle->resorigtbl;
ctle->resorigcol = ptle->resorigcol;
}
result = clean_up_removed_plan_level((Plan *) plan, plan->subplan);
}
else
{
......@@ -1190,6 +1121,30 @@ trivial_subqueryscan(SubqueryScan *plan)
return true;
}
/*
* clean_up_removed_plan_level
* Do necessary cleanup when we strip out a SubqueryScan, Append, etc
*
* We are dropping the "parent" plan in favor of returning just its "child".
* A few small tweaks are needed.
*/
static Plan *
clean_up_removed_plan_level(Plan *parent, Plan *child)
{
/* We have to be sure we don't lose any initplans */
child->initPlan = list_concat(parent->initPlan,
child->initPlan);
/*
* We also have to transfer the parent's column labeling info into the
* child, else columns sent to client will be improperly labeled if this
* is the topmost plan level. resjunk and so on may be important too.
*/
apply_tlist_labeling(child->targetlist, parent->targetlist);
return child;
}
/*
* set_foreignscan_references
* Do set_plan_references processing on a ForeignScan
......@@ -1340,6 +1295,131 @@ set_customscan_references(PlannerInfo *root,
}
}
/*
* set_append_references
* Do set_plan_references processing on an Append
*
* We try to strip out the Append entirely; if we can't, we have
* to do the normal processing on it.
*/
static Plan *
set_append_references(PlannerInfo *root,
Append *aplan,
int rtoffset)
{
ListCell *l;
/*
* Append, like Sort et al, doesn't actually evaluate its targetlist or
* check quals. If it's got exactly one child plan, then it's not doing
* anything useful at all, and we can strip it out.
*/
Assert(aplan->plan.qual == NIL);
/* First, we gotta recurse on the children */
foreach(l, aplan->appendplans)
{
lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
}
/* Now, if there's just one, forget the Append and return that child */
if (list_length(aplan->appendplans) == 1)
return clean_up_removed_plan_level((Plan *) aplan,
(Plan *) linitial(aplan->appendplans));
/*
* Otherwise, clean up the Append as needed. It's okay to do this after
* recursing to the children, because set_dummy_tlist_references doesn't
* look at those.
*/
set_dummy_tlist_references((Plan *) aplan, rtoffset);
if (aplan->part_prune_info)
{
foreach(l, aplan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
/* We don't need to recurse to lefttree or righttree ... */
Assert(aplan->plan.lefttree == NULL);
Assert(aplan->plan.righttree == NULL);
return (Plan *) aplan;
}
/*
* set_mergeappend_references
* Do set_plan_references processing on a MergeAppend
*
* We try to strip out the MergeAppend entirely; if we can't, we have
* to do the normal processing on it.
*/
static Plan *
set_mergeappend_references(PlannerInfo *root,
MergeAppend *mplan,
int rtoffset)
{
ListCell *l;
/*
* MergeAppend, like Sort et al, doesn't actually evaluate its targetlist
* or check quals. If it's got exactly one child plan, then it's not
* doing anything useful at all, and we can strip it out.
*/
Assert(mplan->plan.qual == NIL);
/* First, we gotta recurse on the children */
foreach(l, mplan->mergeplans)
{
lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
}
/* Now, if there's just one, forget the MergeAppend and return that child */
if (list_length(mplan->mergeplans) == 1)
return clean_up_removed_plan_level((Plan *) mplan,
(Plan *) linitial(mplan->mergeplans));
/*
* Otherwise, clean up the MergeAppend as needed. It's okay to do this
* after recursing to the children, because set_dummy_tlist_references
* doesn't look at those.
*/
set_dummy_tlist_references((Plan *) mplan, rtoffset);
if (mplan->part_prune_info)
{
foreach(l, mplan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
/* We don't need to recurse to lefttree or righttree ... */
Assert(mplan->plan.lefttree == NULL);
Assert(mplan->plan.righttree == NULL);
return (Plan *) mplan;
}
/*
* copyVar
* Copy a Var node.
......
......@@ -1242,7 +1242,6 @@ create_append_path(PlannerInfo *root,
pathnode->path.parallel_aware = parallel_aware;
pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = parallel_workers;
pathnode->path.pathkeys = NIL; /* result is always considered unsorted */
pathnode->partitioned_rels = list_copy(partitioned_rels);
/*
......@@ -1276,7 +1275,26 @@ create_append_path(PlannerInfo *root,
Assert(!parallel_aware || pathnode->path.parallel_safe);
/*
* If there's exactly one child path, the Append is a no-op and will be
* discarded later (in setrefs.c); therefore, we can inherit the child's
* size, cost, and pathkeys if any. Otherwise, it's unsorted, and we must
* do the normal costsize calculation.
*/
if (list_length(pathnode->subpaths) == 1)
{
Path *child = (Path *) linitial(pathnode->subpaths);
pathnode->path.rows = child->rows;
pathnode->path.startup_cost = child->startup_cost;
pathnode->path.total_cost = child->total_cost;
pathnode->path.pathkeys = child->pathkeys;
}
else
{
pathnode->path.pathkeys = NIL; /* unsorted if more than 1 subpath */
cost_append(pathnode);
}
/* If the caller provided a row estimate, override the computed value. */
if (rows >= 0)
......@@ -1408,7 +1426,17 @@ create_merge_append_path(PlannerInfo *root,
Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
}
/* Now we can compute total costs of the MergeAppend */
/*
* Now we can compute total costs of the MergeAppend. If there's exactly
* one child path, the MergeAppend is a no-op and will be discarded later
* (in setrefs.c); otherwise we do the normal cost calculation.
*/
if (list_length(subpaths) == 1)
{
pathnode->path.startup_cost = input_startup_cost;
pathnode->path.total_cost = input_total_cost;
}
else
cost_merge_append(&pathnode->path, root,
pathkeys, list_length(subpaths),
input_startup_cost, input_total_cost,
......
......@@ -1771,11 +1771,10 @@ explain (costs off) select * from list_parted;
explain (costs off) select * from list_parted where a is null;
QUERY PLAN
--------------------------------
Append
-> Seq Scan on part_null_xy
--------------------------
Seq Scan on part_null_xy
Filter: (a IS NULL)
(3 rows)
(2 rows)
explain (costs off) select * from list_parted where a is not null;
QUERY PLAN
......@@ -1801,19 +1800,17 @@ explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
QUERY PLAN
---------------------------------------------------------------------------------------
Append
-> Seq Scan on part_ab_cd
---------------------------------------------------------------------------------
Seq Scan on part_ab_cd
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
(3 rows)
(2 rows)
explain (costs off) select * from list_parted where a = 'ab';
QUERY PLAN
------------------------------------------
Append
-> Seq Scan on part_ab_cd
------------------------------------
Seq Scan on part_ab_cd
Filter: ((a)::text = 'ab'::text)
(3 rows)
(2 rows)
create table range_list_parted (
a int,
......@@ -1894,11 +1891,10 @@ explain (costs off) select * from range_list_parted where a is null;
/* Should only select rows from the null-accepting partition */
explain (costs off) select * from range_list_parted where b is null;
QUERY PLAN
------------------------------------
Append
-> Seq Scan on part_40_inf_null
------------------------------
Seq Scan on part_40_inf_null
Filter: (b IS NULL)
(3 rows)
(2 rows)
explain (costs off) select * from range_list_parted where a is not null and a < 67;
QUERY PLAN
......@@ -2022,11 +2018,10 @@ explain (costs off) select * from mcrparted where a > -1; -- scans all partition
explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4
QUERY PLAN
-----------------------------------------------------------
Append
-> Seq Scan on mcrparted4
-----------------------------------------------------
Seq Scan on mcrparted4
Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10))
(3 rows)
(2 rows)
explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def
QUERY PLAN
......@@ -2051,21 +2046,17 @@ create index parted_minmax1i on parted_minmax1 (a, b);
insert into parted_minmax values (1,'12345');
explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
QUERY PLAN
-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Merge Append
Sort Key: parted_minmax1.a
-> Index Only Scan using parted_minmax1i on parted_minmax1
Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
InitPlan 2 (returns $1)
-> Limit
-> Merge Append
Sort Key: parted_minmax1_1.a DESC
-> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1
Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
(13 rows)
(9 rows)
select min(a), max(a) from parted_minmax where b = '12345';
min | max
......
......@@ -187,10 +187,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
QUERY PLAN
-----------------------------------------------------------
-----------------------------------------------------
Sort
Sort Key: t1.a
-> Append
-> Hash Join
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p2 t2
......@@ -198,7 +197,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a <
-> Hash
-> Seq Scan on prt1_p2 t1
Filter: ((a < 450) AND (b = 0))
(10 rows)
(9 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
......@@ -1480,10 +1479,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Seq Scan on prt2_l_p3_p1 t2_3
-> Seq Scan on prt2_l_p3_p2 t2_4
-> Hash
-> Append
-> Seq Scan on prt1_l_p3_p1 t1_3
Filter: (b = 0)
(29 rows)
(28 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
......@@ -1526,10 +1524,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b
-> Seq Scan on prt2_l_p3_p1 t2_3
-> Seq Scan on prt2_l_p3_p2 t2_4
-> Hash
-> Append
-> Seq Scan on prt1_l_p3_p1 t1_3
Filter: (b = 0)
(30 rows)
(29 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
......@@ -1580,10 +1577,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b
-> Seq Scan on prt1_l_p3_p1 t1_3
-> Seq Scan on prt1_l_p3_p2 t1_4
-> Hash
-> Append
-> Seq Scan on prt2_l_p3_p1 t2_3
Filter: (a = 0)
(30 rows)
(29 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b;
a | c | b | c
......@@ -1629,14 +1625,12 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1
Filter: (a = 0)
-> Hash Full Join
Hash Cond: ((prt1_l_p3_p1.a = prt2_l_p3_p1.b) AND ((prt1_l_p3_p1.c)::text = (prt2_l_p3_p1.c)::text))
-> Append
-> Seq Scan on prt1_l_p3_p1
Filter: (b = 0)
-> Hash
-> Append
-> Seq Scan on prt2_l_p3_p1
Filter: (a = 0)
(33 rows)
(31 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b;
a | c | b | c
......@@ -1697,7 +1691,6 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
-> Seq Scan on prt1_l_p2_p2 t2_2
Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text))
-> Nested Loop Left Join
-> Append
-> Seq Scan on prt1_l_p3_p1 t1_3
Filter: (b = 0)
-> Hash Join
......@@ -1711,7 +1704,7 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
-> Seq Scan on prt1_l_p3_p2 t2_4
Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
(45 rows)
(44 rows)
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
......
......@@ -1058,14 +1058,13 @@ NOTICE: f_leak => awesome science fiction
EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
QUERY PLAN
--------------------------------------------------------------------
Append
--------------------------------------------------------------
Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
InitPlan 1 (returns $0)
-> Index Scan using uaccount_pkey on uaccount
Index Cond: (pguser = CURRENT_USER)
-> Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
(6 rows)
(5 rows)
-- pp1 ERROR
INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
......@@ -1137,14 +1136,13 @@ NOTICE: f_leak => awesome science fiction
EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
QUERY PLAN
--------------------------------------------------------------------
Append
--------------------------------------------------------------
Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
InitPlan 1 (returns $0)
-> Index Scan using uaccount_pkey on uaccount
Index Cond: (pguser = CURRENT_USER)
-> Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
(6 rows)
(5 rows)
-- viewpoint from regress_rls_carol
SET SESSION AUTHORIZATION regress_rls_carol;
......
......@@ -813,10 +813,9 @@ explain (costs off)
SELECT 2 AS t, * FROM tenk1 b) c
WHERE t = 2;
QUERY PLAN
---------------------------
Append
-> Seq Scan on tenk1 b
(2 rows)
---------------------
Seq Scan on tenk1 b
(1 row)
-- Test that we push quals into UNION sub-selects only when it's safe
explain (costs off)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment